Skip to content

Commit 6e49fd4

Browse files
authored
Merge pull request #3600 from OpenNeuroOrg/app/contributor-user-search
App/contributor user search
2 parents eae62be + 8ef1de3 commit 6e49fd4

27 files changed

+1063
-544
lines changed
Lines changed: 86 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,111 @@
11
import React from "react"
2-
import { vi } from "vitest"
32
import { fireEvent, render, screen } from "@testing-library/react"
3+
import { vi } from "vitest"
4+
import { act } from "react-dom/test-utils"
5+
import { MockedProvider } from "@apollo/client/testing"
46
import { ContributorFormRow } from "../contributor-form-row"
5-
import type { Contributor } from "../../types/datacite"
7+
import { GET_USERS } from "../../queries/users"
68

7-
describe("ContributorFormRow", () => {
8-
const mockContributor: Contributor = {
9-
name: "Jane Doe",
10-
givenName: "Jane",
11-
familyName: "Doe",
12-
orcid: "",
13-
contributorType: "Researcher",
14-
order: 1,
15-
}
9+
interface Contributor {
10+
name: string
11+
orcid: string
12+
type: string
13+
}
1614

17-
const defaultProps = {
18-
contributor: mockContributor,
15+
interface ContributorFormRowProps {
16+
index: number
17+
value: string
18+
type: string
19+
orcid: string
20+
onChange: (...args: unknown[]) => void
21+
onMove: (...args: unknown[]) => void
22+
onRemove: (...args: unknown[]) => void
23+
contributor: Contributor
24+
errors: Record<string, string>
25+
isFirst: boolean
26+
isLast: boolean
27+
}
28+
29+
describe("ContributorFormRow", () => {
30+
const defaultProps: ContributorFormRowProps = {
1931
index: 0,
20-
errors: {},
32+
value: "Alice",
33+
type: "ContactPerson",
34+
orcid: "0000-0001-2345-6789",
2135
onChange: vi.fn(),
2236
onMove: vi.fn(),
2337
onRemove: vi.fn(),
38+
contributor: {
39+
name: "Alice",
40+
orcid: "0000-0001-2345-6789",
41+
type: "ContactPerson",
42+
},
43+
errors: {},
2444
isFirst: true,
2545
isLast: false,
2646
}
2747

48+
const mocks = [
49+
{
50+
request: {
51+
query: GET_USERS,
52+
variables: { search: "Alice", limit: 100, offset: 0 },
53+
},
54+
result: {
55+
data: {
56+
users: {
57+
users: [
58+
{
59+
id: "1",
60+
name: "Alice",
61+
orcid: "0000-0001-2345-6789",
62+
__typename: "User",
63+
},
64+
],
65+
totalCount: 1,
66+
__typename: "UsersResponse",
67+
},
68+
},
69+
},
70+
},
71+
]
72+
73+
const renderComponent = (props = {}) =>
74+
render(
75+
<MockedProvider mocks={mocks} addTypename={false}>
76+
<ContributorFormRow {...defaultProps} {...props} />
77+
</MockedProvider>,
78+
)
79+
2880
beforeEach(() => {
81+
vi.useFakeTimers()
2982
vi.clearAllMocks()
3083
})
3184

32-
it("renders contributor name input", () => {
33-
render(<ContributorFormRow {...defaultProps} />)
34-
expect(screen.getByPlaceholderText("Name")).toHaveValue("Jane Doe")
85+
afterEach(() => {
86+
vi.useRealTimers()
3587
})
3688

37-
it("calls onChange when name is updated", () => {
38-
render(<ContributorFormRow {...defaultProps} />)
39-
const input = screen.getByPlaceholderText("Name")
40-
fireEvent.change(input, { target: { value: "New Name" } })
41-
expect(defaultProps.onChange).toHaveBeenCalledWith(0, "name", "New Name")
89+
it("shows input value correctly", () => {
90+
renderComponent()
91+
expect(screen.getByDisplayValue("Alice")).toBeInTheDocument()
4292
})
4393

44-
it("calls onMove when up or down buttons are clicked", () => {
45-
render(<ContributorFormRow {...defaultProps} isFirst={false} />)
46-
const upButton = screen.getByText("↑")
47-
fireEvent.click(upButton)
48-
expect(defaultProps.onMove).toHaveBeenCalledWith(0, "up")
49-
})
94+
it("calls onMove and onRemove callbacks", async () => {
95+
renderComponent()
96+
97+
const _upButton = screen.getByRole("button", { name: "↑" })
98+
const downButton = screen.getByRole("button", { name: "↓" })
99+
const removeButton = screen.getByRole("button", { name: "" })
100+
101+
await act(async () => {
102+
fireEvent.click(downButton)
103+
})
104+
expect(defaultProps.onMove).toHaveBeenCalledWith(0, "down")
50105

51-
it("calls onRemove when trash button is clicked", () => {
52-
render(<ContributorFormRow {...defaultProps} />)
53-
const trashButton = screen.getByRole("button", { name: "" })
54-
fireEvent.click(trashButton)
106+
await act(async () => {
107+
fireEvent.click(removeButton)
108+
})
55109
expect(defaultProps.onRemove).toHaveBeenCalledWith(0)
56110
})
57111
})

packages/openneuro-app/src/scripts/contributors/__tests__/contributor-list.spec.tsx

Lines changed: 82 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from "react"
22
import { fireEvent, render, screen } from "@testing-library/react"
33
import { MockedProvider } from "@apollo/client/testing"
44
import { ContributorsListDisplay } from "../contributors-list"
5+
import { GET_USERS } from "../../queries/users"
56
import type { Contributor } from "../../types/datacite"
67

78
describe("ContributorsListDisplay", () => {
@@ -24,9 +25,65 @@ describe("ContributorsListDisplay", () => {
2425
},
2526
]
2627

27-
const renderComponent = (props = {}) => {
28-
return render(
29-
<MockedProvider>
28+
const mocks = [
29+
{
30+
request: {
31+
query: GET_USERS,
32+
variables: { search: "Jane Doe", limit: 10, offset: 0 },
33+
},
34+
result: {
35+
data: {
36+
users: {
37+
users: [{
38+
id: "1",
39+
name: "Jane Doe",
40+
avatar: "",
41+
orcid: "",
42+
__typename: "User",
43+
}],
44+
totalCount: 1,
45+
__typename: "UsersResponse",
46+
},
47+
},
48+
},
49+
},
50+
{
51+
request: {
52+
query: GET_USERS,
53+
variables: { search: "John Smith", limit: 10, offset: 0 },
54+
},
55+
result: {
56+
data: {
57+
users: {
58+
users: [{
59+
id: "2",
60+
name: "John Smith",
61+
avatar: "",
62+
orcid: "",
63+
__typename: "User",
64+
}],
65+
totalCount: 1,
66+
__typename: "UsersResponse",
67+
},
68+
},
69+
},
70+
},
71+
{
72+
request: {
73+
query: GET_USERS,
74+
variables: { search: "", limit: 10, offset: 0 },
75+
},
76+
result: {
77+
data: {
78+
users: { users: [], totalCount: 0, __typename: "UsersResponse" },
79+
},
80+
},
81+
},
82+
]
83+
84+
const renderComponent = (props = {}) =>
85+
render(
86+
<MockedProvider mocks={mocks} addTypename={false}>
3087
<ContributorsListDisplay
3188
contributors={baseContributors}
3289
datasetId="ds000001"
@@ -35,12 +92,22 @@ describe("ContributorsListDisplay", () => {
3592
/>
3693
</MockedProvider>,
3794
)
38-
}
3995

4096
it("renders contributors list", async () => {
4197
renderComponent()
42-
expect(await screen.findByText(/Jane Doe/)).toBeInTheDocument()
43-
expect(screen.getByText(/John Smith/)).toBeInTheDocument()
98+
// Match Jane Doe
99+
expect(
100+
await screen.findByText((content) =>
101+
["Jane", "Doe"].every((part) => content.includes(part))
102+
),
103+
).toBeInTheDocument()
104+
105+
// Match John Smith
106+
expect(
107+
screen.getByText((content) =>
108+
["John", "Smith"].every((part) => content.includes(part))
109+
),
110+
).toBeInTheDocument()
44111
})
45112

46113
it("shows edit button when editable", async () => {
@@ -50,11 +117,12 @@ describe("ContributorsListDisplay", () => {
50117

51118
it("enters edit mode when edit button clicked", async () => {
52119
renderComponent()
53-
54120
const editButton = await screen.findByText("Edit")
55121
fireEvent.click(editButton)
56122

57-
const nameInputs = await screen.findAllByPlaceholderText("Name")
123+
const nameInputs = await screen.findAllByPlaceholderText(
124+
"Type name or ORCID (or add new)",
125+
)
58126
expect(nameInputs.length).toBe(2)
59127
expect(nameInputs[0]).toHaveValue("Jane Doe")
60128
expect(nameInputs[1]).toHaveValue("John Smith")
@@ -68,12 +136,13 @@ describe("ContributorsListDisplay", () => {
68136
const addButton = await screen.findByText("Add Contributor")
69137
fireEvent.click(addButton)
70138

71-
expect(await screen.findAllByPlaceholderText("Name")).toHaveLength(3)
139+
expect(
140+
await screen.findAllByPlaceholderText("Type name or ORCID (or add new)"),
141+
).toHaveLength(3)
72142
})
73143

74144
it("shows an error when trying to submit with empty name", async () => {
75145
renderComponent()
76-
77146
const editButton = await screen.findByText("Edit")
78147
fireEvent.click(editButton)
79148

@@ -83,9 +152,9 @@ describe("ContributorsListDisplay", () => {
83152
const saveButton = await screen.findByText("Save")
84153
fireEvent.click(saveButton)
85154

86-
// Check that the new field has the browser validation pseudo-state
87-
const nameInputs = await screen.findAllByPlaceholderText("Name")
88-
155+
const nameInputs = await screen.findAllByPlaceholderText(
156+
"Type name or ORCID (or add new)",
157+
)
89158
expect(nameInputs[2]).toBeInvalid()
90159
expect(nameInputs[2]).toBeRequired()
91160
})

0 commit comments

Comments
 (0)