@jobber/components-native 0.47.3 → 0.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -64,6 +64,10 @@ export interface SelectProps {
64
64
  * The validations that will mark this component as invalid
65
65
  */
66
66
  readonly validations?: RegisterOptions;
67
+ /**
68
+ * Used to locate this view in end-to-end tests.
69
+ */
70
+ readonly testID?: string;
67
71
  }
68
- export declare function Select({ value, defaultValue, onChange, children, placeholder, label, assistiveText, disabled, invalid, validations, accessibilityLabel, name, }: SelectProps): JSX.Element;
72
+ export declare function Select({ value, defaultValue, onChange, children, placeholder, label, assistiveText, disabled, invalid, validations, accessibilityLabel, name, testID, }: SelectProps): JSX.Element;
69
73
  export declare function Option({ children }: SelectOption): JSX.Element;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jobber/components-native",
3
- "version": "0.47.3",
3
+ "version": "0.48.0",
4
4
  "license": "MIT",
5
5
  "description": "React Native implementation of Atlantis",
6
6
  "repository": {
@@ -84,5 +84,5 @@
84
84
  "react-native-reanimated": "^2.17.0",
85
85
  "react-native-safe-area-context": "^4.5.2"
86
86
  },
87
- "gitHead": "6e42ff7d712d09ecd2214b0fccae8c39bc51afec"
87
+ "gitHead": "faaa9887b344a34eb2760e71fee532145e2dbe81"
88
88
  }
@@ -80,48 +80,6 @@ describe("Select", () => {
80
80
  ).toBeDefined();
81
81
  });
82
82
 
83
- describe("when invalid", () => {
84
- const labelText = "labelText";
85
-
86
- it("renders an invalid Select", () => {
87
- const { getByText } = render(
88
- <Select onChange={onChange} invalid={true} label={labelText}>
89
- <Option value={"1"}>1</Option>
90
- <Option value={"2"}>2</Option>
91
- </Select>,
92
- );
93
- expect(
94
- getByText(labelText, { includeHiddenElements: true }).props.style,
95
- ).toContainEqual({
96
- color: tokens["color-critical"],
97
- });
98
- });
99
-
100
- it("renders an invalid Select with placeholder", () => {
101
- const placeholder = "Place me in the holder";
102
- const { getByText } = render(
103
- <Select
104
- label={labelText}
105
- onChange={onChange}
106
- invalid={true}
107
- placeholder={placeholder}
108
- >
109
- <Option value={"1"}>1</Option>
110
- <Option value={"2"}>2</Option>
111
- </Select>,
112
- );
113
-
114
- expect(
115
- getByText(placeholder, { includeHiddenElements: true }),
116
- ).toBeDefined();
117
- expect(
118
- getByText(labelText, { includeHiddenElements: true }).props.style,
119
- ).toContainEqual({
120
- color: tokens["color-critical"],
121
- });
122
- });
123
- });
124
-
125
83
  it("renders a disabled Select", () => {
126
84
  const labelText = "labelText";
127
85
  const { getByText } = render(
@@ -166,156 +124,172 @@ describe("Select", () => {
166
124
  ).toBeDefined();
167
125
  });
168
126
 
169
- describe("fires the onChange callback", () => {
170
- it("fires", () => {
171
- const { getByTestId } = render(
172
- <Select onChange={onChange} value={"2"}>
173
- <Option value={"1"}>1</Option>
174
- <Option value={"2"}>2</Option>
175
- </Select>,
176
- );
127
+ it("renders a Select with custom testID", () => {
128
+ const testID = "testID";
129
+ const { getByTestId } = render(
130
+ <Select onChange={onChange} testID={testID}>
131
+ <Option value={"1"}>1</Option>
132
+ <Option value={"2"}>2</Option>
133
+ </Select>,
134
+ );
177
135
 
178
- const select = getByTestId("ATL-Select").findByType(SelectInternalPicker);
179
- expect(select).toBeTruthy();
180
- fireEvent(select, "onChange", "1");
181
- expect(onChange).toHaveBeenCalledWith("1");
136
+ expect(getByTestId(`ATL-${testID}-Select`)).toBeDefined();
137
+ });
138
+
139
+ it("renders an accessibilityLabel if provided", () => {
140
+ const { getByLabelText } = render(
141
+ <Select
142
+ onChange={onChange}
143
+ label="label"
144
+ accessibilityLabel="accessibilityLabel"
145
+ >
146
+ <Option value={"1"}>1</Option>
147
+ <Option value={"2"}>2</Option>
148
+ </Select>,
149
+ );
150
+
151
+ expect(getByLabelText("accessibilityLabel")).toBeTruthy();
152
+ });
153
+ it("fires the onChange callback", () => {
154
+ const { getByTestId } = render(
155
+ <Select onChange={onChange} value={"2"}>
156
+ <Option value={"1"}>1</Option>
157
+ <Option value={"2"}>2</Option>
158
+ </Select>,
159
+ );
160
+
161
+ const select = getByTestId("ATL-Select").findByType(SelectInternalPicker);
162
+ expect(select).toBeTruthy();
163
+ fireEvent(select, "onChange", "1");
164
+ expect(onChange).toHaveBeenCalledWith("1");
165
+ });
166
+ });
167
+
168
+ describe("when Select is invalid", () => {
169
+ const labelText = "labelText";
170
+
171
+ it("renders an invalid Select", () => {
172
+ const { getByText } = render(
173
+ <Select onChange={onChange} invalid={true} label={labelText}>
174
+ <Option value={"1"}>1</Option>
175
+ <Option value={"2"}>2</Option>
176
+ </Select>,
177
+ );
178
+ expect(
179
+ getByText(labelText, { includeHiddenElements: true }).props.style,
180
+ ).toContainEqual({
181
+ color: tokens["color-critical"],
182
182
  });
183
183
  });
184
184
 
185
- describe("Invalid value", () => {
186
- it("renders with the empty value option", () => {
187
- const { getByText } = render(
188
- <Select onChange={onChange} value={"invalid"}>
189
- <Option value={"first"}>first</Option>
190
- <Option value={"2"}>2</Option>
191
- </Select>,
192
- );
185
+ it("renders an invalid Select with placeholder", () => {
186
+ const placeholder = "Place me in the holder";
187
+ const { getByText } = render(
188
+ <Select
189
+ label={labelText}
190
+ onChange={onChange}
191
+ invalid={true}
192
+ placeholder={placeholder}
193
+ >
194
+ <Option value={"1"}>1</Option>
195
+ <Option value={"2"}>2</Option>
196
+ </Select>,
197
+ );
193
198
 
194
- expect(
195
- getByText(defaultPlaceholder, { includeHiddenElements: true }),
196
- ).toBeDefined();
199
+ expect(
200
+ getByText(placeholder, { includeHiddenElements: true }),
201
+ ).toBeDefined();
202
+ expect(
203
+ getByText(labelText, { includeHiddenElements: true }).props.style,
204
+ ).toContainEqual({
205
+ color: tokens["color-critical"],
197
206
  });
207
+ });
208
+ });
198
209
 
199
- it("renders with the placeholder", () => {
200
- const { getByText } = render(
210
+ describe("when validations are passed to the component", () => {
211
+ describe("validations fail", () => {
212
+ let tree: RenderAPI;
213
+ const labelText = "labelText";
214
+ const errorMsg = "Too short";
215
+ beforeEach(() => {
216
+ tree = render(
201
217
  <Select
218
+ label={labelText}
202
219
  onChange={onChange}
203
- value={"invalid"}
204
- placeholder={"Make a selection"}
220
+ value={"Watermelon"}
221
+ validations={{
222
+ minLength: { value: 60, message: errorMsg },
223
+ }}
205
224
  >
206
- <Option value={"1"}>1</Option>
207
- <Option value={"2"}>2</Option>
225
+ <Option value={"Apple"}>Apple</Option>
226
+ <Option value={"Watermelon"}>Watermelon</Option>
208
227
  </Select>,
209
228
  );
229
+ });
230
+
231
+ it("renders the error message when there is an error", async () => {
232
+ const select = tree
233
+ .getByTestId("ATL-Select")
234
+ .findByType(SelectInternalPicker);
235
+ fireEvent(select, "onChange", "Apple");
236
+ expect(
237
+ await tree.findByText(errorMsg, { includeHiddenElements: true }),
238
+ ).toBeTruthy();
239
+ });
210
240
 
241
+ it("shows the invalid colours", async () => {
242
+ const select = tree
243
+ .getByTestId("ATL-Select")
244
+ .findByType(SelectInternalPicker);
245
+ fireEvent(select, "onChange", "Apple");
211
246
  expect(
212
- getByText("Make a selection", { includeHiddenElements: true }),
213
- ).toBeDefined();
247
+ (await tree.findByText(labelText, { includeHiddenElements: true }))
248
+ .props.style,
249
+ ).toContainEqual({
250
+ color: tokens["color-critical"],
251
+ });
214
252
  });
215
253
  });
216
254
 
217
- describe("accessibilityLabel", () => {
218
- it("uses accessibilityLabel if specified", () => {
219
- const { getByLabelText } = render(
255
+ describe("validations passes", () => {
256
+ let tree: RenderAPI;
257
+ const labelText = "labelText";
258
+ const errorMsg = "Not too short";
259
+ beforeEach(() => {
260
+ tree = render(
220
261
  <Select
262
+ label={labelText}
221
263
  onChange={onChange}
222
- label="label"
223
- accessibilityLabel="accessibilityLabel"
264
+ value={"Watermelon"}
265
+ validations={{
266
+ minLength: { value: 4, message: errorMsg },
267
+ }}
224
268
  >
225
- <Option value={"1"}>1</Option>
226
- <Option value={"2"}>2</Option>
269
+ <Option value={"Apple"}>Apple</Option>
270
+ <Option value={"Watermelon"}>Watermelon</Option>
227
271
  </Select>,
228
272
  );
229
-
230
- expect(getByLabelText("accessibilityLabel")).toBeTruthy();
231
273
  });
232
- });
233
274
 
234
- describe("when validations are passed to the component", () => {
235
- describe("validations fail", () => {
236
- let tree: RenderAPI;
237
- const labelText = "labelText";
238
- const errorMsg = "Too short";
239
- beforeEach(() => {
240
- tree = render(
241
- <Select
242
- label={labelText}
243
- onChange={onChange}
244
- value={"Watermelon"}
245
- validations={{
246
- minLength: { value: 60, message: errorMsg },
247
- }}
248
- >
249
- <Option value={"Apple"}>Apple</Option>
250
- <Option value={"Watermelon"}>Watermelon</Option>
251
- </Select>,
252
- );
253
- });
254
-
255
- it("renders the error message when there is an error", async () => {
256
- const select = tree
257
- .getByTestId("ATL-Select")
258
- .findByType(SelectInternalPicker);
259
- fireEvent(select, "onChange", "Apple");
260
- expect(
261
- await tree.findByText(errorMsg, { includeHiddenElements: true }),
262
- ).toBeTruthy();
263
- });
264
-
265
- it("shows the invalid colours", async () => {
266
- const select = tree
267
- .getByTestId("ATL-Select")
268
- .findByType(SelectInternalPicker);
269
- fireEvent(select, "onChange", "Apple");
270
- expect(
271
- (await tree.findByText(labelText, { includeHiddenElements: true }))
272
- .props.style,
273
- ).toContainEqual({
274
- color: tokens["color-critical"],
275
- });
276
- });
275
+ it("does not render any error messages", () => {
276
+ const select = tree
277
+ .getByTestId("ATL-Select")
278
+ .findByType(SelectInternalPicker);
279
+ expect(select).toBeTruthy();
280
+ fireEvent(select, "onChange", "Apple");
281
+ expect(tree.queryByText(errorMsg)).toBeNull();
277
282
  });
278
283
 
279
- describe("validations passes", () => {
280
- let tree: RenderAPI;
281
- const labelText = "labelText";
282
- const errorMsg = "Not too short";
283
- beforeEach(() => {
284
- tree = render(
285
- <Select
286
- label={labelText}
287
- onChange={onChange}
288
- value={"Watermelon"}
289
- validations={{
290
- minLength: { value: 4, message: errorMsg },
291
- }}
292
- >
293
- <Option value={"Apple"}>Apple</Option>
294
- <Option value={"Watermelon"}>Watermelon</Option>
295
- </Select>,
296
- );
297
- });
298
-
299
- it("does not render any error messages", () => {
300
- const select = tree
301
- .getByTestId("ATL-Select")
302
- .findByType(SelectInternalPicker);
303
- expect(select).toBeTruthy();
304
- fireEvent(select, "onChange", "Apple");
305
- expect(tree.queryByText(errorMsg)).toBeNull();
306
- });
307
-
308
- it("has non-critical colours", () => {
309
- const select = tree
310
- .getByTestId("ATL-Select")
311
- .findByType(SelectInternalPicker);
312
- fireEvent(select, "onChange", "Apple");
313
- expect(
314
- tree.getByText(labelText, { includeHiddenElements: true }).props
315
- .style,
316
- ).toContainEqual({
317
- color: tokens["color-text--secondary"],
318
- });
284
+ it("has non-critical colours", () => {
285
+ const select = tree
286
+ .getByTestId("ATL-Select")
287
+ .findByType(SelectInternalPicker);
288
+ fireEvent(select, "onChange", "Apple");
289
+ expect(
290
+ tree.getByText(labelText, { includeHiddenElements: true }).props.style,
291
+ ).toContainEqual({
292
+ color: tokens["color-text--secondary"],
319
293
  });
320
294
  });
321
295
  });
@@ -88,6 +88,11 @@ export interface SelectProps {
88
88
  * The validations that will mark this component as invalid
89
89
  */
90
90
  readonly validations?: RegisterOptions;
91
+
92
+ /**
93
+ * Used to locate this view in end-to-end tests.
94
+ */
95
+ readonly testID?: string;
91
96
  }
92
97
 
93
98
  export function Select({
@@ -103,6 +108,7 @@ export function Select({
103
108
  validations,
104
109
  accessibilityLabel,
105
110
  name,
111
+ testID,
106
112
  }: SelectProps): JSX.Element {
107
113
  const { field, error } = useFormController({
108
114
  name,
@@ -129,7 +135,7 @@ export function Select({
129
135
  }}
130
136
  >
131
137
  <View
132
- testID="ATL-Select"
138
+ testID={getTestID(testID)}
133
139
  accessible={true}
134
140
  accessibilityLabel={getA11yLabel()}
135
141
  accessibilityValue={{ text: getValue() }}
@@ -187,6 +193,7 @@ export function Select({
187
193
  function getA11yLabel(): string | undefined {
188
194
  let text = [accessibilityLabel || label, assistiveText];
189
195
  text = text.filter(Boolean);
196
+
190
197
  return text.join(", ");
191
198
  }
192
199
 
@@ -219,6 +226,7 @@ export function Select({
219
226
  const options = getOptions();
220
227
 
221
228
  const activeValue = options.find(option => option.isActive);
229
+
222
230
  return activeValue?.label || placeholder || t("Select.emptyValue");
223
231
  }
224
232
  }
@@ -229,9 +237,18 @@ function getTextVariation({
229
237
  }: Pick<SelectProps, "invalid" | "disabled">): TextVariation {
230
238
  if (invalid) return "error";
231
239
  if (disabled) return "disabled";
240
+
232
241
  return "subdued";
233
242
  }
234
243
 
244
+ function getTestID(testID?: string): string {
245
+ if (testID) {
246
+ return `ATL-${testID}-Select`;
247
+ }
248
+
249
+ return "ATL-Select";
250
+ }
251
+
235
252
  export function Option({ children }: SelectOption): JSX.Element {
236
253
  return <>{children}</>;
237
254
  }