@wordpress/dataviews 4.10.0 → 4.11.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.
Files changed (119) hide show
  1. package/CHANGELOG.md +19 -9
  2. package/LICENSE.md +1 -1
  3. package/README.md +309 -175
  4. package/build/components/dataviews/index.js +12 -1
  5. package/build/components/dataviews/index.js.map +1 -1
  6. package/build/components/dataviews-context/index.js +2 -1
  7. package/build/components/dataviews-context/index.js.map +1 -1
  8. package/build/components/dataviews-filters/add-filter.js +35 -30
  9. package/build/components/dataviews-filters/add-filter.js.map +1 -1
  10. package/build/components/dataviews-filters/index.js +3 -1
  11. package/build/components/dataviews-filters/index.js.map +1 -1
  12. package/build/components/dataviews-item-actions/index.js +24 -19
  13. package/build/components/dataviews-item-actions/index.js.map +1 -1
  14. package/build/components/dataviews-layout/index.js +2 -0
  15. package/build/components/dataviews-layout/index.js.map +1 -1
  16. package/build/components/dataviews-view-config/index.js +48 -43
  17. package/build/components/dataviews-view-config/index.js.map +1 -1
  18. package/build/dataviews-layouts/grid/index.js +12 -5
  19. package/build/dataviews-layouts/grid/index.js.map +1 -1
  20. package/build/dataviews-layouts/grid/preview-size-picker.js +22 -25
  21. package/build/dataviews-layouts/grid/preview-size-picker.js.map +1 -1
  22. package/build/dataviews-layouts/list/index.js +27 -20
  23. package/build/dataviews-layouts/list/index.js.map +1 -1
  24. package/build/dataviews-layouts/table/column-header-menu.js +102 -99
  25. package/build/dataviews-layouts/table/column-header-menu.js.map +1 -1
  26. package/build/dataviews-layouts/table/column-primary.js +7 -3
  27. package/build/dataviews-layouts/table/column-primary.js.map +1 -1
  28. package/build/dataviews-layouts/table/index.js +4 -0
  29. package/build/dataviews-layouts/table/index.js.map +1 -1
  30. package/build/types.js.map +1 -1
  31. package/build-module/components/dataviews/index.js +12 -1
  32. package/build-module/components/dataviews/index.js.map +1 -1
  33. package/build-module/components/dataviews-context/index.js +2 -1
  34. package/build-module/components/dataviews-context/index.js.map +1 -1
  35. package/build-module/components/dataviews-filters/add-filter.js +36 -31
  36. package/build-module/components/dataviews-filters/add-filter.js.map +1 -1
  37. package/build-module/components/dataviews-filters/index.js +3 -1
  38. package/build-module/components/dataviews-filters/index.js.map +1 -1
  39. package/build-module/components/dataviews-item-actions/index.js +24 -19
  40. package/build-module/components/dataviews-item-actions/index.js.map +1 -1
  41. package/build-module/components/dataviews-layout/index.js +2 -0
  42. package/build-module/components/dataviews-layout/index.js.map +1 -1
  43. package/build-module/components/dataviews-view-config/index.js +49 -44
  44. package/build-module/components/dataviews-view-config/index.js.map +1 -1
  45. package/build-module/dataviews-layouts/grid/index.js +14 -7
  46. package/build-module/dataviews-layouts/grid/index.js.map +1 -1
  47. package/build-module/dataviews-layouts/grid/preview-size-picker.js +22 -25
  48. package/build-module/dataviews-layouts/grid/preview-size-picker.js.map +1 -1
  49. package/build-module/dataviews-layouts/list/index.js +27 -20
  50. package/build-module/dataviews-layouts/list/index.js.map +1 -1
  51. package/build-module/dataviews-layouts/table/column-header-menu.js +102 -99
  52. package/build-module/dataviews-layouts/table/column-header-menu.js.map +1 -1
  53. package/build-module/dataviews-layouts/table/column-primary.js +7 -3
  54. package/build-module/dataviews-layouts/table/column-primary.js.map +1 -1
  55. package/build-module/dataviews-layouts/table/index.js +4 -0
  56. package/build-module/dataviews-layouts/table/index.js.map +1 -1
  57. package/build-module/types.js.map +1 -1
  58. package/build-style/style-rtl.css +45 -55
  59. package/build-style/style.css +45 -55
  60. package/build-types/components/dataviews/index.d.ts +2 -1
  61. package/build-types/components/dataviews/index.d.ts.map +1 -1
  62. package/build-types/components/dataviews-context/index.d.ts +2 -0
  63. package/build-types/components/dataviews-context/index.d.ts.map +1 -1
  64. package/build-types/components/dataviews-filters/add-filter.d.ts +3 -2
  65. package/build-types/components/dataviews-filters/add-filter.d.ts.map +1 -1
  66. package/build-types/components/dataviews-item-actions/index.d.ts.map +1 -1
  67. package/build-types/components/dataviews-layout/index.d.ts.map +1 -1
  68. package/build-types/components/dataviews-view-config/index.d.ts.map +1 -1
  69. package/build-types/dataviews-layouts/grid/index.d.ts.map +1 -1
  70. package/build-types/dataviews-layouts/grid/preview-size-picker.d.ts.map +1 -1
  71. package/build-types/dataviews-layouts/list/index.d.ts.map +1 -1
  72. package/build-types/dataviews-layouts/table/column-header-menu.d.ts.map +1 -1
  73. package/build-types/dataviews-layouts/table/column-primary.d.ts +2 -1
  74. package/build-types/dataviews-layouts/table/column-primary.d.ts.map +1 -1
  75. package/build-types/dataviews-layouts/table/index.d.ts +1 -1
  76. package/build-types/dataviews-layouts/table/index.d.ts.map +1 -1
  77. package/build-types/test/dataform.d.ts +2 -0
  78. package/build-types/test/dataform.d.ts.map +1 -0
  79. package/build-types/test/dataviews.d.ts +2 -0
  80. package/build-types/test/dataviews.d.ts.map +1 -0
  81. package/build-types/test/normalize-fields.d.ts +2 -0
  82. package/build-types/test/normalize-fields.d.ts.map +1 -0
  83. package/build-types/test/validation.d.ts +2 -0
  84. package/build-types/test/validation.d.ts.map +1 -0
  85. package/build-types/types.d.ts +5 -0
  86. package/build-types/types.d.ts.map +1 -1
  87. package/build-wp/index.js +1209 -999
  88. package/package.json +13 -12
  89. package/src/components/dataviews/index.tsx +15 -1
  90. package/src/components/dataviews/style.scss +0 -1
  91. package/src/components/dataviews-context/index.ts +3 -0
  92. package/src/components/dataviews-filters/add-filter.tsx +43 -39
  93. package/src/components/dataviews-filters/index.tsx +1 -1
  94. package/src/components/dataviews-footer/style.scss +0 -3
  95. package/src/components/dataviews-item-actions/index.tsx +25 -27
  96. package/src/components/dataviews-layout/index.tsx +2 -0
  97. package/src/components/dataviews-view-config/index.tsx +52 -43
  98. package/src/components/dataviews-view-config/style.scss +0 -1
  99. package/src/dataviews-layouts/grid/index.tsx +17 -5
  100. package/src/dataviews-layouts/grid/preview-size-picker.tsx +25 -30
  101. package/src/dataviews-layouts/grid/style.scss +24 -33
  102. package/src/dataviews-layouts/list/index.tsx +35 -27
  103. package/src/dataviews-layouts/list/style.scss +5 -5
  104. package/src/dataviews-layouts/table/column-header-menu.tsx +152 -148
  105. package/src/dataviews-layouts/table/column-primary.tsx +7 -0
  106. package/src/dataviews-layouts/table/index.tsx +10 -0
  107. package/src/dataviews-layouts/table/style.scss +0 -1
  108. package/src/test/dataform.tsx +348 -0
  109. package/src/test/dataviews.tsx +380 -0
  110. package/src/types.ts +6 -0
  111. package/tsconfig.json +14 -4
  112. package/tsconfig.tsbuildinfo +1 -1
  113. package/build/components/form-field-visibility/index.js +0 -32
  114. package/build/components/form-field-visibility/index.js.map +0 -1
  115. package/build-module/components/form-field-visibility/index.js +0 -26
  116. package/build-module/components/form-field-visibility/index.js.map +0 -1
  117. package/build-types/components/form-field-visibility/index.d.ts +0 -11
  118. package/build-types/components/form-field-visibility/index.d.ts.map +0 -1
  119. package/src/components/form-field-visibility/index.tsx +0 -32
@@ -0,0 +1,380 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { render, screen } from '@testing-library/react';
5
+ import userEvent from '@testing-library/user-event';
6
+
7
+ /**
8
+ * WordPress dependencies
9
+ */
10
+ import { useMemo, useState } from '@wordpress/element';
11
+
12
+ /**
13
+ * Internal dependencies
14
+ */
15
+ import DataViews from '../components/dataviews';
16
+ import { LAYOUT_GRID, LAYOUT_LIST, LAYOUT_TABLE } from '../constants';
17
+ import type { Action, View } from '../types';
18
+ import { filterSortAndPaginate } from '../filter-and-sort-data-view';
19
+
20
+ type Data = {
21
+ id: number;
22
+ title: string;
23
+ author?: number;
24
+ order?: number;
25
+ };
26
+
27
+ const DEFAULT_VIEW = {
28
+ type: 'table' as const,
29
+ search: '',
30
+ page: 1,
31
+ perPage: 10,
32
+ layout: {},
33
+ filters: [],
34
+ };
35
+
36
+ const defaultLayouts = {
37
+ [ LAYOUT_TABLE ]: {},
38
+ [ LAYOUT_GRID ]: {},
39
+ [ LAYOUT_LIST ]: {},
40
+ };
41
+
42
+ const fields = [
43
+ {
44
+ id: 'title',
45
+ label: 'Title',
46
+ type: 'text' as const,
47
+ },
48
+ {
49
+ id: 'order',
50
+ label: 'Order',
51
+ type: 'integer' as const,
52
+ },
53
+ {
54
+ id: 'author',
55
+ label: 'Author',
56
+ type: 'integer' as const,
57
+ elements: [
58
+ { value: 1, label: 'Jane' },
59
+ { value: 2, label: 'John' },
60
+ ],
61
+ },
62
+ {
63
+ label: 'Image',
64
+ id: 'image',
65
+ render: ( { item }: { item: Data } ) => {
66
+ return (
67
+ <svg
68
+ width="400"
69
+ height="180"
70
+ data-testid={ 'image-field-' + item.id }
71
+ >
72
+ <rect
73
+ x="50"
74
+ y="20"
75
+ rx="20"
76
+ ry="20"
77
+ width="150"
78
+ height="150"
79
+ style={ { fill: 'red', opacity: 0.5 } }
80
+ />
81
+ </svg>
82
+ );
83
+ },
84
+ enableSorting: false,
85
+ },
86
+ ];
87
+
88
+ const actions: Action< Data >[] = [
89
+ {
90
+ id: 'delete',
91
+ label: 'Delete',
92
+ isDestructive: true,
93
+ supportsBulk: true,
94
+ RenderModal: () => <div>Modal Content</div>,
95
+ },
96
+ ];
97
+
98
+ const data: Data[] = [
99
+ {
100
+ id: 1,
101
+ title: 'Hello World',
102
+ author: 1,
103
+ order: 1,
104
+ },
105
+ {
106
+ id: 2,
107
+ title: 'Homepage',
108
+ author: 2,
109
+ order: 1,
110
+ },
111
+ {
112
+ id: 3,
113
+ title: 'Posts',
114
+ author: 2,
115
+ order: 1,
116
+ },
117
+ ];
118
+
119
+ function DataViewWrapper( {
120
+ view: additionalView,
121
+ ...props
122
+ }: Partial< Parameters< typeof DataViews< Data > >[ 0 ] > ) {
123
+ const [ view, setView ] = useState< View >( {
124
+ ...DEFAULT_VIEW,
125
+ fields: [ 'title', 'order', 'author' ],
126
+ ...additionalView,
127
+ } );
128
+
129
+ const { data: shownData, paginationInfo } = useMemo( () => {
130
+ return filterSortAndPaginate( data, view, props.fields || fields );
131
+ }, [ view, props.fields ] );
132
+
133
+ const dataViewProps = {
134
+ getItemId: ( item: Data ) => item.id.toString(),
135
+ paginationInfo,
136
+ data: shownData,
137
+ view,
138
+ fields,
139
+ onChangeView: setView,
140
+ actions: [],
141
+ defaultLayouts,
142
+ ...props,
143
+ };
144
+
145
+ return <DataViews { ...dataViewProps } />;
146
+ }
147
+
148
+ // jest.useFakeTimers();
149
+
150
+ describe( 'DataViews component', () => {
151
+ it( 'should show "No results" if data is empty', () => {
152
+ render( <DataViewWrapper data={ [] } /> );
153
+ expect( screen.getByText( 'No results' ) ).toBeInTheDocument();
154
+ } );
155
+
156
+ it( 'should filter results by "search" text, if field has enableGlobalSearch set to true', async () => {
157
+ const fieldsWithSearch = [
158
+ {
159
+ ...fields[ 0 ],
160
+ enableGlobalSearch: true,
161
+ },
162
+ fields[ 1 ],
163
+ ];
164
+ render(
165
+ <DataViewWrapper
166
+ fields={ fieldsWithSearch }
167
+ view={ { ...DEFAULT_VIEW, search: 'Hello' } }
168
+ />
169
+ );
170
+ // Row count includes header.
171
+ expect( screen.getAllByRole( 'row' ).length ).toEqual( 2 );
172
+ expect( screen.getByText( 'Hello World' ) ).toBeInTheDocument();
173
+ } );
174
+
175
+ it( 'should display matched element label if field contains elements list', () => {
176
+ render(
177
+ <DataViewWrapper
178
+ data={ [ { id: 1, author: 3, title: 'Hello World' } ] }
179
+ fields={ [
180
+ {
181
+ id: 'author',
182
+ label: 'Author',
183
+ type: 'integer' as const,
184
+ elements: [
185
+ { value: 1, label: 'Jane' },
186
+ { value: 2, label: 'John' },
187
+ { value: 3, label: 'Tim' },
188
+ ],
189
+ },
190
+ ] }
191
+ />
192
+ );
193
+ expect( screen.getByText( 'Tim' ) ).toBeInTheDocument();
194
+ } );
195
+
196
+ it( 'should render custom render function if defined in field definition', () => {
197
+ render(
198
+ <DataViewWrapper
199
+ data={ [ { id: 1, title: 'Test Title' } ] }
200
+ fields={ [
201
+ {
202
+ id: 'title',
203
+ label: 'Title',
204
+ type: 'text' as const,
205
+ render: ( { item }: { item: Data } ) => {
206
+ return item.title?.toUpperCase();
207
+ },
208
+ },
209
+ ] }
210
+ />
211
+ );
212
+ expect( screen.getByText( 'TEST TITLE' ) ).toBeInTheDocument();
213
+ } );
214
+
215
+ describe( 'in table view', () => {
216
+ it( 'should display columns for each field', () => {
217
+ render( <DataViewWrapper /> );
218
+ const displayedColumnFields = fields.filter( ( field ) =>
219
+ [ 'title', 'order', 'author' ].includes( field.id )
220
+ );
221
+ for ( const field of displayedColumnFields ) {
222
+ expect(
223
+ screen.getByRole( 'button', { name: field.label } )
224
+ ).toBeInTheDocument();
225
+ }
226
+ } );
227
+
228
+ it( 'should display the passed in data', () => {
229
+ render( <DataViewWrapper /> );
230
+ for ( const item of data ) {
231
+ expect(
232
+ screen.getAllByText( item.title )[ 0 ]
233
+ ).toBeInTheDocument();
234
+ }
235
+ } );
236
+
237
+ it( 'should display title column if defined using titleField', () => {
238
+ render(
239
+ <DataViewWrapper
240
+ view={ {
241
+ ...DEFAULT_VIEW,
242
+ fields: [ 'order', 'author' ],
243
+ titleField: 'title',
244
+ } }
245
+ />
246
+ );
247
+ for ( const item of data ) {
248
+ expect(
249
+ screen.getAllByText( item.title )[ 0 ]
250
+ ).toBeInTheDocument();
251
+ }
252
+ } );
253
+
254
+ it( 'should render actions column if actions are supported and passed in', () => {
255
+ render( <DataViewWrapper actions={ actions } /> );
256
+ expect( screen.getByText( 'Actions' ) ).toBeInTheDocument();
257
+ } );
258
+
259
+ it( 'should trigger the onClickItem callback if isItemClickable returns true and title field is clicked', async () => {
260
+ const onClickItemCallback = jest.fn();
261
+
262
+ render(
263
+ <DataViewWrapper
264
+ view={ {
265
+ ...DEFAULT_VIEW,
266
+ fields: [ 'author' ],
267
+ titleField: 'title',
268
+ } }
269
+ actions={ actions }
270
+ isItemClickable={ () => true }
271
+ onClickItem={ onClickItemCallback }
272
+ />
273
+ );
274
+ const titleField = screen.getByText( data[ 0 ].title );
275
+ const user = userEvent.setup();
276
+ await user.click( titleField );
277
+ expect( onClickItemCallback ).toHaveBeenCalledWith( data[ 0 ] );
278
+ } );
279
+ } );
280
+
281
+ describe( 'in grid view', () => {
282
+ it( 'should display the passed in data', () => {
283
+ render(
284
+ <DataViewWrapper
285
+ view={ {
286
+ type: 'grid',
287
+ } }
288
+ />
289
+ );
290
+ for ( const item of data ) {
291
+ expect(
292
+ screen.getAllByText( item.title )[ 0 ]
293
+ ).toBeInTheDocument();
294
+ }
295
+ } );
296
+
297
+ it( 'should render mediaField if defined', () => {
298
+ render(
299
+ <DataViewWrapper
300
+ view={ {
301
+ type: 'grid',
302
+ mediaField: 'image',
303
+ } }
304
+ />
305
+ );
306
+ for ( const item of data ) {
307
+ expect(
308
+ screen.getByTestId( 'image-field-' + item.id )
309
+ ).toBeInTheDocument();
310
+ }
311
+ } );
312
+
313
+ it( 'should render actions dropdown if actions are supported and passed in for each grid item', () => {
314
+ render(
315
+ <DataViewWrapper
316
+ view={ {
317
+ type: 'grid',
318
+ } }
319
+ actions={ actions }
320
+ />
321
+ );
322
+ expect(
323
+ screen.getAllByRole( 'button', { name: 'Actions' } ).length
324
+ ).toEqual( 3 );
325
+ } );
326
+
327
+ it( 'should trigger the onClickItem callback if isItemClickable returns true and a media field is clicked', async () => {
328
+ const mediaClickItemCallback = jest.fn();
329
+
330
+ render(
331
+ <DataViewWrapper
332
+ view={ {
333
+ type: 'grid',
334
+ mediaField: 'image',
335
+ } }
336
+ actions={ actions }
337
+ isItemClickable={ () => true }
338
+ onClickItem={ mediaClickItemCallback }
339
+ />
340
+ );
341
+ const imageField = screen.getByTestId(
342
+ 'image-field-' + data[ 0 ].id
343
+ );
344
+ const user = userEvent.setup();
345
+ await user.click( imageField );
346
+ expect( mediaClickItemCallback ).toHaveBeenCalledWith( data[ 0 ] );
347
+ } );
348
+ } );
349
+
350
+ describe( 'in list view', () => {
351
+ it( 'should display the passed in data', () => {
352
+ render(
353
+ <DataViewWrapper
354
+ view={ {
355
+ type: 'list',
356
+ } }
357
+ />
358
+ );
359
+ for ( const item of data ) {
360
+ expect(
361
+ screen.getAllByText( item.title )[ 0 ]
362
+ ).toBeInTheDocument();
363
+ }
364
+ } );
365
+
366
+ it( 'should render actions dropdown if actions are supported and passed in for each list item', () => {
367
+ render(
368
+ <DataViewWrapper
369
+ view={ {
370
+ type: 'list',
371
+ } }
372
+ actions={ actions }
373
+ />
374
+ );
375
+ expect(
376
+ screen.getAllByRole( 'button', { name: 'Actions' } ).length
377
+ ).toEqual( 3 );
378
+ } );
379
+ } );
380
+ } );
package/src/types.ts CHANGED
@@ -322,6 +322,11 @@ interface ViewBase {
322
322
  * Whether to show the description
323
323
  */
324
324
  showDescription?: boolean;
325
+
326
+ /**
327
+ * Whether to show the hierarchical levels.
328
+ */
329
+ showLevels?: boolean;
325
330
  }
326
331
 
327
332
  export interface ColumnStyle {
@@ -480,6 +485,7 @@ export interface ViewBaseProps< Item > {
480
485
  data: Item[];
481
486
  fields: NormalizedField< Item >[];
482
487
  getItemId: ( item: Item ) => string;
488
+ getItemLevel?: ( item: Item ) => number;
483
489
  isLoading?: boolean;
484
490
  onChangeView: ( view: View ) => void;
485
491
  onChangeSelection: SetSelection;
package/tsconfig.json CHANGED
@@ -2,9 +2,12 @@
2
2
  "$schema": "https://json.schemastore.org/tsconfig.json",
3
3
  "extends": "../../tsconfig.base.json",
4
4
  "compilerOptions": {
5
- "rootDir": "src",
6
- "declarationDir": "build-types",
7
- "checkJs": false
5
+ "types": [
6
+ "gutenberg-env",
7
+ "gutenberg-test-env",
8
+ "jest",
9
+ "@testing-library/jest-dom"
10
+ ]
8
11
  },
9
12
  "references": [
10
13
  { "path": "../components" },
@@ -17,5 +20,12 @@
17
20
  { "path": "../private-apis" },
18
21
  { "path": "../warning" }
19
22
  ],
20
- "include": [ "src" ]
23
+ "exclude": [
24
+ "src/**/*.android.js",
25
+ "src/**/*.ios.js",
26
+ "src/**/*.native.js",
27
+ "src/**/react-native-*",
28
+ "src/**/stories/**/*.js", // only exclude js files, tsx files should be checked
29
+ "src/**/test/**/*.js" // only exclude js files, ts{x} files should be checked
30
+ ]
21
31
  }