@linzjs/step-ag-grid 8.4.2 → 8.4.3

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.
Files changed (35) hide show
  1. package/dist/src/lui/TextAreaInput.d.ts +1 -0
  2. package/dist/src/lui/TextInputFormatted.d.ts +1 -0
  3. package/dist/src/react-menu3/components/FocusableItem.d.ts +1 -1
  4. package/dist/src/react-menu3/components/MenuItem.d.ts +3 -1
  5. package/dist/src/react-menu3/components/SubMenu.d.ts +1 -1
  6. package/dist/src/react-menu3/utils/withHovering.d.ts +2 -2
  7. package/dist/step-ag-grid.esm.js +46 -54
  8. package/dist/step-ag-grid.esm.js.map +1 -1
  9. package/package.json +1 -1
  10. package/src/components/gridForm/GridFormDropDown.tsx +10 -12
  11. package/src/components/gridForm/GridFormMultiSelect.tsx +19 -23
  12. package/src/components/gridForm/GridFormPopoverMenu.tsx +18 -22
  13. package/src/components/gridForm/GridFormSubComponentTextArea.tsx +1 -0
  14. package/src/components/gridForm/GridFormSubComponentTextInput.tsx +1 -0
  15. package/src/components/gridForm/GridFormTextArea.tsx +1 -1
  16. package/src/components/gridForm/GridFormTextInput.tsx +1 -1
  17. package/src/lui/TextAreaInput.tsx +2 -0
  18. package/src/lui/TextInputFormatted.tsx +2 -0
  19. package/src/react-menu3/components/MenuItem.tsx +1 -1
  20. package/src/react-menu3/utils/withHovering.tsx +3 -3
  21. package/src/stories/grid/GridPopoutEditDropDown.stories.tsx +10 -2
  22. package/src/stories/grid/gridFormInteraction/GridFormDropDownInteraction.stories.tsx +128 -0
  23. package/src/stories/grid/gridFormInteraction/GridFormEditBearingCorrectionInteraction.stories.tsx +92 -0
  24. package/src/stories/grid/gridFormInteraction/GridFormEditBearingInteraction.stories.tsx +91 -0
  25. package/src/stories/grid/gridFormInteraction/GridFormPopoverMenuInteraction.stories.tsx +171 -0
  26. package/src/stories/grid/gridFormInteraction/GridFormTextAreaInteraction.stories.tsx +78 -0
  27. package/src/stories/grid/gridFormInteraction/GridFormTextInputInteraction.stories.tsx +85 -0
  28. package/src/stories/grid/{gridForm → gridFormStatic}/GridFormDropDown.stories.tsx +1 -1
  29. package/src/stories/grid/{gridForm → gridFormStatic}/GridFormEditBearing.stories.tsx +1 -1
  30. package/src/stories/grid/{gridForm → gridFormStatic}/GridFormEditBearingCorrection.stories.tsx +1 -1
  31. package/src/stories/grid/{gridForm → gridFormStatic}/GridFormMessage.stories.tsx +1 -1
  32. package/src/stories/grid/{gridForm → gridFormStatic}/GridFormMultiSelect.stories.tsx +1 -1
  33. package/src/stories/grid/{gridForm → gridFormStatic}/GridFormPopoverMenu.stories.tsx +1 -1
  34. package/src/stories/grid/{gridForm → gridFormStatic}/GridFormTextArea.stories.tsx +1 -1
  35. package/src/stories/grid/{gridForm → gridFormStatic}/GridFormTextInput.stories.tsx +1 -1
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@linzjs/step-ag-grid",
3
3
  "repository": "github:linz/step-ag-grid.git",
4
4
  "license": "MIT",
5
- "version": "8.4.2",
5
+ "version": "8.4.3",
6
6
  "keywords": [
7
7
  "aggrid",
8
8
  "ag-grid",
@@ -8,7 +8,7 @@ import { CellEditorCommon } from "../GridCell";
8
8
  import { useGridPopoverHook } from "../GridPopoverHook";
9
9
  import { useGridPopoverContext } from "../../contexts/GridPopoverContext";
10
10
  import { GridSubComponentContext } from "../../contexts/GridSubComponentContext";
11
- import { ClickEvent, MenuInstance } from "../../react-menu3/types";
11
+ import { ClickEvent } from "../../react-menu3/types";
12
12
  import { FormError } from "../../lui/FormError";
13
13
  import { isNotEmpty } from "../../utils/util";
14
14
 
@@ -270,19 +270,17 @@ export const GridFormDropDown = <RowType extends GridBaseRow>(props: GridFormDro
270
270
  title={item.disabled && typeof item.disabled !== "boolean" ? item.disabled : ""}
271
271
  value={item.value}
272
272
  onFocus={() => {
273
- setSelectedItem(item);
274
- if (item.subComponent) {
275
- subComponentIsValid.current = true;
276
- subComponentInitialValue.current = null;
277
- } else {
273
+ if (selectedItem !== item) {
274
+ setSelectedItem(item);
278
275
  setSubSelectedValue(null);
279
276
  subComponentIsValid.current = true;
277
+ if (item.subComponent) {
278
+ subComponentInitialValue.current = null;
279
+ }
280
280
  }
281
281
  }}
282
282
  onClick={(e: ClickEvent) => {
283
- if (item.subComponent) {
284
- e.keepOpen = true;
285
- }
283
+ e.keepOpen = !!item.subComponent;
286
284
  }}
287
285
  >
288
286
  {item.label ?? (item.value == null ? `<${item.value}>` : `${item.value}`)}
@@ -291,7 +289,7 @@ export const GridFormDropDown = <RowType extends GridBaseRow>(props: GridFormDro
291
289
 
292
290
  {item.subComponent && selectedItem === item && (
293
291
  <FocusableItem className={"LuiDeprecatedForms"} key={`${item.label}_subcomponent`}>
294
- {(ref: MenuInstance) => (
292
+ {() => (
295
293
  <GridSubComponentContext.Provider
296
294
  value={{
297
295
  context: { options },
@@ -308,13 +306,13 @@ export const GridFormDropDown = <RowType extends GridBaseRow>(props: GridFormDro
308
306
  subComponentIsValid.current = valid;
309
307
  },
310
308
  triggerSave: async () => {
311
- ref.closeMenu();
309
+ // empty
312
310
  },
313
311
  }}
314
312
  >
315
313
  {item.subComponent && (
316
314
  <div className={"subComponent"}>
317
- <item.subComponent key={`${fieldToString(field)}-${index}_subcomponent_inner`} />
315
+ <item.subComponent />
318
316
  </div>
319
317
  )}
320
318
  </GridSubComponentContext.Provider>
@@ -397,29 +397,25 @@ const MenuSubComponent = (props: {
397
397
  const { data, item, options, setOptions, subComponentIsValid, triggerSave } = props;
398
398
  return (
399
399
  <FocusableItem className={"LuiDeprecatedForms"} key={`${item.value}_subcomponent`}>
400
- {(_: any) =>
401
- item.subComponent && (
402
- <GridSubComponentContext.Provider
403
- value={{
404
- context: { options },
405
- data,
406
- value: item.subValue,
407
- setValue: (value: any) => {
408
- item.subValue = value;
409
- setOptions([...options]);
410
- },
411
- setValid: (valid: boolean) => {
412
- subComponentIsValid[`${item.value}`] = valid;
413
- },
414
- triggerSave,
415
- }}
416
- >
417
- <div className={"subComponent"}>
418
- <item.subComponent />
419
- </div>
420
- </GridSubComponentContext.Provider>
421
- )
422
- }
400
+ {() => (
401
+ <GridSubComponentContext.Provider
402
+ value={{
403
+ context: { options },
404
+ data,
405
+ value: item.subValue,
406
+ setValue: (value: any) => {
407
+ item.subValue = value;
408
+ setOptions([...options]);
409
+ },
410
+ setValid: (valid: boolean) => {
411
+ subComponentIsValid[`${item.value}`] = valid;
412
+ },
413
+ triggerSave,
414
+ }}
415
+ >
416
+ <div className={"subComponent"}>{item.subComponent && <item.subComponent />}</div>
417
+ </GridSubComponentContext.Provider>
418
+ )}
423
419
  </FocusableItem>
424
420
  );
425
421
  };
@@ -143,28 +143,24 @@ export const GridFormPopoverMenu = <RowType extends GridBaseRow>(props: GridForm
143
143
  </MenuItem>
144
144
  {item.subComponent && subComponentSelected === item && (
145
145
  <FocusableItem className={"LuiDeprecatedForms"} key={`${item.label}_subcomponent`}>
146
- {(_: any) =>
147
- item.subComponent && (
148
- <GridSubComponentContext.Provider
149
- value={{
150
- context: {},
151
- data,
152
- value: subSelectedValue,
153
- setValue: (value: any) => {
154
- setSubSelectedValue(value);
155
- },
156
- setValid: (valid: boolean) => {
157
- subComponentIsValid.current = valid;
158
- },
159
- triggerSave,
160
- }}
161
- >
162
- <div className={"subComponent"}>
163
- <item.subComponent />
164
- </div>
165
- </GridSubComponentContext.Provider>
166
- )
167
- }
146
+ {() => (
147
+ <GridSubComponentContext.Provider
148
+ value={{
149
+ context: {},
150
+ data,
151
+ value: subSelectedValue,
152
+ setValue: (value: any) => {
153
+ setSubSelectedValue(value);
154
+ },
155
+ setValid: (valid: boolean) => {
156
+ subComponentIsValid.current = valid;
157
+ },
158
+ triggerSave,
159
+ }}
160
+ >
161
+ <div className={"subComponent"}>{item.subComponent && <item.subComponent />}</div>
162
+ </GridSubComponentContext.Provider>
163
+ )}
168
164
  </FocusableItem>
169
165
  )}
170
166
  </Fragment>
@@ -44,6 +44,7 @@ export const GridFormSubComponentTextArea = <RowType extends GridBaseRow>(
44
44
  helpText={helpText}
45
45
  autoFocus={true}
46
46
  placeholder={props.placeholder}
47
+ allowTabToSave={true}
47
48
  />
48
49
  </div>
49
50
  );
@@ -41,6 +41,7 @@ export const GridFormSubComponentTextInput = <RowType extends GridBaseRow>(
41
41
  autoFocus={true}
42
42
  placeholder={props.placeholder}
43
43
  style={{ width: "100%" }}
44
+ allowTabToSave={true}
44
45
  />
45
46
  );
46
47
  };
@@ -55,7 +55,7 @@ export const GridFormTextArea = <RowType extends GridBaseRow>(props: GridFormTex
55
55
  value={value}
56
56
  onChange={(e) => setValue(e.target.value)}
57
57
  error={invalid()}
58
- placeholder={props.placeholder}
58
+ placeholder={props.placeholder ?? "Type here"}
59
59
  helpText={helpText}
60
60
  />
61
61
  </div>,
@@ -57,7 +57,7 @@ export const GridFormTextInput = <RowType extends GridBaseRow>(props: GridFormTe
57
57
  error={invalid()}
58
58
  formatted={props.units}
59
59
  style={{ width: props.width ?? 240 }}
60
- placeholder={props.placeholder}
60
+ placeholder={props.placeholder ?? "Type here"}
61
61
  helpText={helpText}
62
62
  />
63
63
  </div>,
@@ -18,6 +18,7 @@ export interface LuiTextAreaInputProps extends InputHTMLAttributes<HTMLTextAreaE
18
18
  mandatory?: boolean;
19
19
  helpText?: string;
20
20
  error?: JSX.Element | string | boolean | null;
21
+ allowTabToSave?: boolean;
21
22
  }
22
23
 
23
24
  export const TextAreaInput = (props: LuiTextAreaInputProps) => {
@@ -50,6 +51,7 @@ export const TextAreaInput = (props: LuiTextAreaInputProps) => {
50
51
  }
51
52
  props.onMouseEnter && props.onMouseEnter(e);
52
53
  }}
54
+ data-allowtabtosave={props.allowTabToSave}
53
55
  >
54
56
  {props.value}
55
57
  </textarea>
@@ -14,6 +14,7 @@ export interface LuiTextInputProps extends DetailedHTMLProps<InputHTMLAttributes
14
14
  helpText?: string;
15
15
  error?: JSX.Element | string | boolean | null;
16
16
  formatted?: string;
17
+ allowTabToSave?: boolean;
17
18
  }
18
19
 
19
20
  export const TextInputFormatted = (props: LuiTextInputProps): JSX.Element => {
@@ -31,6 +32,7 @@ export const TextInputFormatted = (props: LuiTextInputProps): JSX.Element => {
31
32
  e.currentTarget.focus();
32
33
  props.onMouseEnter && props.onMouseEnter(e);
33
34
  }}
35
+ data-allowtabtosave={props.allowTabToSave}
34
36
  />
35
37
  <span className={"LuiTextInput-formatted"}>{props.formatted}</span>
36
38
  </span>
@@ -128,7 +128,7 @@ const MenuItemFr = ({
128
128
  };
129
129
 
130
130
  /**
131
- * Keyboard events are triggered on up, otherwise sub-components get spaces and enters typed in them
131
+ * Keyboard events are triggered on up, otherwise subcomponents get spaces and enters typed in them
132
132
  */
133
133
  const handleKeyUp = (e: KeyboardEvent) => {
134
134
  if (!isHovering) return;
@@ -1,4 +1,4 @@
1
- import { memo, forwardRef, useContext, useRef, MutableRefObject } from "react";
1
+ import { memo, forwardRef, useContext, useRef, MutableRefObject, PropsWithRef } from "react";
2
2
  import { HoverItemContext } from "../contexts/HoverItemContext";
3
3
 
4
4
  export interface withHoveringResultProps {
@@ -7,9 +7,9 @@ export interface withHoveringResultProps {
7
7
  menuItemRef?: MutableRefObject<any>;
8
8
  }
9
9
 
10
- export const withHovering = (name: string, WrappedComponent: (...args: any) => JSX.Element): any => {
10
+ export const withHovering = <T,>(name: string, WrappedComponent: (props: T) => JSX.Element) => {
11
11
  const Component = memo(WrappedComponent);
12
- const WithHovering = forwardRef<any>((props: any, ref) => {
12
+ const WithHovering = forwardRef((props: PropsWithRef<T>, ref) => {
13
13
  const menuItemRef = useRef<any>(null);
14
14
  return (
15
15
  <Component
@@ -14,6 +14,7 @@ import { ColDefT, GridCell } from "../../components/GridCell";
14
14
  import { GridPopoverEditDropDown } from "../../components/gridPopoverEdit/GridPopoverEditDropDown";
15
15
  import { GridFormSubComponentTextInput } from "../../components/gridForm/GridFormSubComponentTextInput";
16
16
  import { GridPopoverMenu } from "../../components/gridPopoverEdit/GridPopoverMenu";
17
+ import { GridFormSubComponentTextArea } from "../../components/gridForm/GridFormSubComponentTextArea";
17
18
 
18
19
  export default {
19
20
  title: "Components / Grids",
@@ -234,9 +235,16 @@ const GridEditDropDownTemplate: ComponentStory<typeof Grid> = (props: GridProps)
234
235
  },
235
236
  {
236
237
  value: "oth",
237
- label: "Other",
238
+ label: "Other text input",
238
239
  subComponent: () => (
239
- <GridFormSubComponentTextInput placeholder={"Other..."} defaultValue={""} required={true} />
240
+ <GridFormSubComponentTextInput placeholder={"Other..."} defaultValue={"a"} required={true} />
241
+ ),
242
+ },
243
+ {
244
+ value: "oth",
245
+ label: "Other text area",
246
+ subComponent: () => (
247
+ <GridFormSubComponentTextArea placeholder={"Other..."} defaultValue={"b"} required={true} />
240
248
  ),
241
249
  },
242
250
  ];
@@ -0,0 +1,128 @@
1
+ import "@linzjs/lui/dist/scss/base.scss";
2
+ import "@linzjs/lui/dist/fonts";
3
+
4
+ import { useRef } from "react";
5
+ import { ComponentMeta, ComponentStory } from "@storybook/react/dist/ts3.9/client/preview/types-6-3";
6
+ import {
7
+ GridFormDropDown,
8
+ GridFormDropDownProps,
9
+ GridPopoutEditDropDownSelectedItem,
10
+ } from "../../../components/gridForm/GridFormDropDown";
11
+ import { GridContextProvider } from "../../../contexts/GridContextProvider";
12
+ import { GridPopoverContext, GridPopoverContextType } from "contexts/GridPopoverContext";
13
+ import { GridFormSubComponentTextInput } from "../../../components/gridForm/GridFormSubComponentTextInput";
14
+ import { userEvent, within } from "@storybook/testing-library";
15
+ import { expect, jest } from "@storybook/jest";
16
+
17
+ export default {
18
+ title: "GridForm / Interactions",
19
+ component: GridFormDropDown,
20
+ args: {},
21
+ } as ComponentMeta<typeof GridFormDropDown>;
22
+
23
+ const updateValue = jest
24
+ .fn<void, [saveFn: (selectedRows: any[]) => Promise<boolean>, _tabDirection: 1 | 0 | -1]>()
25
+ .mockImplementation((saveFn: (selectedRows: any[]) => Promise<boolean>, _tabDirection: 1 | 0 | -1) => saveFn([]));
26
+
27
+ const onSelectedItem = jest.fn<Promise<void>, [GridPopoutEditDropDownSelectedItem<any>]>().mockResolvedValue(undefined);
28
+
29
+ const Template: ComponentStory<typeof GridFormDropDown> = (props) => {
30
+ const config: GridFormDropDownProps<any> = {
31
+ filtered: "local",
32
+ onSelectedItem,
33
+ options: [
34
+ { label: "Enabled", value: 1 },
35
+ { label: "Disabled", value: 0, disabled: true },
36
+ {
37
+ label: "Sub menu",
38
+ value: 0,
39
+ subComponent: () => (
40
+ <GridFormSubComponentTextInput placeholder={"Text input"} maxLength={5} required defaultValue={""} />
41
+ ),
42
+ },
43
+ ],
44
+ };
45
+ const anchorRef = useRef<HTMLHeadingElement>(null);
46
+
47
+ return (
48
+ <div className={"react-menu-inline-test"}>
49
+ <GridContextProvider>
50
+ <div>
51
+ <h6 ref={anchorRef}>Interaction test</h6>
52
+ <GridPopoverContext.Provider
53
+ value={
54
+ {
55
+ anchorRef: anchorRef,
56
+ updateValue,
57
+ data: { value: "" },
58
+ value: "",
59
+ field: "value",
60
+ selectedRows: [],
61
+ } as any as GridPopoverContextType<any>
62
+ }
63
+ >
64
+ <GridFormDropDown {...props} {...config} />
65
+ </GridPopoverContext.Provider>
66
+ </div>
67
+ </GridContextProvider>
68
+ </div>
69
+ );
70
+ };
71
+
72
+ export const GridFormDropDownInteractions_ = Template.bind({});
73
+ GridFormDropDownInteractions_.play = async ({ canvasElement }) => {
74
+ const canvas = within(canvasElement);
75
+
76
+ const getOption = (name: string) => canvas.findByRole("menuitem", { name });
77
+
78
+ // Check enabled menu handles click
79
+ const enabledMenuOption = await getOption("Enabled");
80
+ expect(enabledMenuOption).toBeInTheDocument();
81
+
82
+ userEvent.click(enabledMenuOption);
83
+ expect(updateValue).toHaveBeenCalled();
84
+ expect(onSelectedItem).toHaveBeenCalled();
85
+
86
+ // Check disabled menu ignores click
87
+ updateValue.mockClear();
88
+ onSelectedItem.mockClear();
89
+ const disabledMenuOption = await getOption("Disabled");
90
+ userEvent.click(disabledMenuOption);
91
+ expect(updateValue).not.toHaveBeenCalled();
92
+ expect(onSelectedItem).not.toHaveBeenCalled();
93
+
94
+ // Check sub menu works
95
+ const subTextInput = await getOption("Sub menu...");
96
+ expect(subTextInput).toBeInTheDocument();
97
+
98
+ expect(canvas.queryByPlaceholderText("Text input")).not.toBeInTheDocument();
99
+
100
+ userEvent.click(subTextInput);
101
+ const textInput = await canvas.findByPlaceholderText("Text input");
102
+ expect(textInput).toBeInTheDocument();
103
+ expect(await canvas.findByText("Must not be empty")).toBeInTheDocument();
104
+
105
+ userEvent.type(textInput, "Hello");
106
+ expect(await canvas.findByText("Press enter or tab to save")).toBeInTheDocument();
107
+
108
+ // Test tab to save
109
+ updateValue.mockClear();
110
+ userEvent.tab();
111
+ expect(updateValue).toHaveBeenCalledWith(expect.anything(), 1); // 1 = Tab
112
+
113
+ // Test shift+tab to save
114
+ updateValue.mockClear();
115
+ userEvent.tab({ shift: true });
116
+ expect(updateValue).toHaveBeenCalledWith(expect.anything(), -1); // -1 = Shift + tab
117
+
118
+ // Test escape to not save
119
+ updateValue.mockClear();
120
+ userEvent.type(textInput, "{Escape}");
121
+ expect(updateValue).not.toHaveBeenCalled();
122
+
123
+ // Test invalid value doesn't save
124
+ updateValue.mockClear();
125
+ userEvent.clear(textInput);
126
+ userEvent.type(textInput, "{Enter}");
127
+ expect(updateValue).not.toHaveBeenCalled();
128
+ };
@@ -0,0 +1,92 @@
1
+ import "@linzjs/lui/dist/scss/base.scss";
2
+ import "@linzjs/lui/dist/fonts";
3
+
4
+ import { ComponentMeta, ComponentStory } from "@storybook/react/dist/ts3.9/client/preview/types-6-3";
5
+ import { GridFormEditBearing } from "../../../components/gridForm/GridFormEditBearing";
6
+ import { GridContextProvider } from "../../../contexts/GridContextProvider";
7
+ import { GridPopoverContext, GridPopoverContextType } from "contexts/GridPopoverContext";
8
+ import { useRef } from "react";
9
+ import { GridPopoverEditBearingCorrectionEditorParams } from "../../../components/gridPopoverEdit/GridPopoverEditBearing";
10
+ import { userEvent, within } from "@storybook/testing-library";
11
+ import { expect, jest } from "@storybook/jest";
12
+
13
+ export default {
14
+ title: "GridForm / Interactions",
15
+ component: GridFormEditBearing,
16
+ args: {},
17
+ } as ComponentMeta<typeof GridFormEditBearing>;
18
+
19
+ const updateValue = jest.fn();
20
+
21
+ const Template: ComponentStory<typeof GridFormEditBearing> = (props) => {
22
+ const anchorRef = useRef<HTMLHeadingElement>(null);
23
+
24
+ return (
25
+ <div className={"react-menu-inline-test"}>
26
+ <GridContextProvider>
27
+ <h6 ref={anchorRef}>Test</h6>
28
+ <GridPopoverContext.Provider
29
+ value={
30
+ {
31
+ anchorRef,
32
+ value: null,
33
+ updateValue,
34
+ } as any as GridPopoverContextType<any>
35
+ }
36
+ >
37
+ <GridFormEditBearing {...props} {...GridPopoverEditBearingCorrectionEditorParams} />
38
+ </GridPopoverContext.Provider>
39
+ </GridContextProvider>
40
+ </div>
41
+ );
42
+ };
43
+
44
+ export const GridFormEditBearingCorrectionInteractions_ = Template.bind({});
45
+ GridFormEditBearingCorrectionInteractions_.play = async ({ canvasElement }) => {
46
+ const canvas = within(canvasElement);
47
+
48
+ expect(await canvas.findByText("Press enter or tab to save")).toBeInTheDocument();
49
+
50
+ const inputField = canvas.getByPlaceholderText("Enter bearing correction");
51
+
52
+ // Test formatting null
53
+ expect(await canvas.findByText("–")).toBeInTheDocument();
54
+
55
+ // Test formatting a bearing
56
+ expect(inputField).toBeInTheDocument();
57
+ userEvent.type(inputField, "1.2345");
58
+ expect(await canvas.findByText("+1° 23' 45.0\"")).toBeInTheDocument();
59
+
60
+ // Test enter to save
61
+ updateValue.mockClear();
62
+ userEvent.type(inputField, "{Enter}");
63
+ expect(updateValue).toHaveBeenCalledWith(expect.anything(), 0); // 0 = Enter
64
+
65
+ // Test tab to save
66
+ updateValue.mockClear();
67
+ userEvent.tab();
68
+ expect(updateValue).toHaveBeenCalledWith(expect.anything(), 1); // 1 = Tab
69
+
70
+ // Test shift+tab to save
71
+ updateValue.mockClear();
72
+ userEvent.tab({ shift: true });
73
+ expect(updateValue).toHaveBeenCalledWith(expect.anything(), -1); // -1 = Shift + tab
74
+
75
+ // Test escape not to save
76
+ updateValue.mockClear();
77
+ userEvent.type(inputField, "{Escape}");
78
+ expect(updateValue).not.toHaveBeenCalled();
79
+
80
+ // Test invalid value doesn't save
81
+ updateValue.mockClear();
82
+ userEvent.type(inputField, "xxx");
83
+ expect(await canvas.findByText("?")).toBeInTheDocument();
84
+
85
+ expect(canvas.getByText("Bearing must be a number in D.MMSSS format")).toBeInTheDocument();
86
+ userEvent.type(inputField, "{Enter}");
87
+ expect(updateValue).not.toHaveBeenCalled();
88
+ userEvent.tab();
89
+ expect(updateValue).not.toHaveBeenCalled();
90
+ userEvent.tab({ shift: true });
91
+ expect(updateValue).not.toHaveBeenCalled();
92
+ };
@@ -0,0 +1,91 @@
1
+ import "@linzjs/lui/dist/scss/base.scss";
2
+ import "@linzjs/lui/dist/fonts";
3
+
4
+ import { ComponentMeta, ComponentStory } from "@storybook/react/dist/ts3.9/client/preview/types-6-3";
5
+ import { GridFormEditBearing } from "../../../components/gridForm/GridFormEditBearing";
6
+ import { GridContextProvider } from "../../../contexts/GridContextProvider";
7
+ import { GridPopoverContext, GridPopoverContextType } from "contexts/GridPopoverContext";
8
+ import { useRef } from "react";
9
+ import { GridPopoverEditBearingEditorParams } from "../../../components/gridPopoverEdit/GridPopoverEditBearing";
10
+ import { userEvent, within } from "@storybook/testing-library";
11
+ import { expect, jest } from "@storybook/jest";
12
+
13
+ export default {
14
+ title: "GridForm / Interactions",
15
+ component: GridFormEditBearing,
16
+ args: {},
17
+ } as ComponentMeta<typeof GridFormEditBearing>;
18
+
19
+ const updateValue = jest.fn();
20
+
21
+ const Template: ComponentStory<typeof GridFormEditBearing> = (props) => {
22
+ const anchorRef = useRef<HTMLHeadingElement>(null);
23
+
24
+ return (
25
+ <div className={"react-menu-inline-test"}>
26
+ <GridContextProvider>
27
+ <h6 ref={anchorRef}>Test</h6>
28
+ <GridPopoverContext.Provider
29
+ value={
30
+ {
31
+ anchorRef,
32
+ value: null,
33
+ updateValue,
34
+ } as any as GridPopoverContextType<any>
35
+ }
36
+ >
37
+ <GridFormEditBearing {...props} {...GridPopoverEditBearingEditorParams} />
38
+ </GridPopoverContext.Provider>
39
+ </GridContextProvider>
40
+ </div>
41
+ );
42
+ };
43
+
44
+ export const GridFormEditBearingInteractions_ = Template.bind({});
45
+ GridFormEditBearingInteractions_.play = async ({ canvasElement }) => {
46
+ const canvas = within(canvasElement);
47
+
48
+ expect(await canvas.findByText("Press enter or tab to save")).toBeInTheDocument();
49
+
50
+ const inputField = canvas.getByPlaceholderText("Enter bearing");
51
+
52
+ // Test formatting null
53
+ expect(await canvas.findByText("–")).toBeInTheDocument();
54
+
55
+ // Test formatting a bearing
56
+ expect(inputField).toBeInTheDocument();
57
+ userEvent.type(inputField, "1.2345");
58
+ expect(await canvas.findByText("1° 23' 45\"")).toBeInTheDocument();
59
+
60
+ // Test enter to save
61
+ updateValue.mockClear();
62
+ userEvent.type(inputField, "{Enter}");
63
+ expect(updateValue).toHaveBeenCalledWith(expect.anything(), 0); // 0 = Enter
64
+
65
+ // Test tab to save
66
+ updateValue.mockClear();
67
+ userEvent.tab();
68
+ expect(updateValue).toHaveBeenCalledWith(expect.anything(), 1); // 1 = Tab
69
+
70
+ // Test shift+tab to save
71
+ updateValue.mockClear();
72
+ userEvent.tab({ shift: true });
73
+ expect(updateValue).toHaveBeenCalledWith(expect.anything(), -1); // -1 = Shift + tab
74
+
75
+ // Test escape not to save
76
+ updateValue.mockClear();
77
+ userEvent.type(inputField, "{Escape}");
78
+ expect(updateValue).not.toHaveBeenCalled();
79
+
80
+ // Test invalid value doesn't save
81
+ updateValue.mockClear();
82
+ userEvent.type(inputField, "xxx");
83
+ expect(await canvas.findByText("?")).toBeInTheDocument();
84
+ expect(canvas.getByText("Bearing must be a number in D.MMSSS format")).toBeInTheDocument();
85
+ userEvent.type(inputField, "{Enter}");
86
+ expect(updateValue).not.toHaveBeenCalled();
87
+ userEvent.tab();
88
+ expect(updateValue).not.toHaveBeenCalled();
89
+ userEvent.tab({ shift: true });
90
+ expect(updateValue).not.toHaveBeenCalled();
91
+ };