ezfw-core 1.0.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.
- package/components/EzBaseComponent.ts +648 -0
- package/components/EzComponent.ts +89 -0
- package/components/EzInput.module.scss +183 -0
- package/components/EzInput.ts +104 -0
- package/components/EzLabel.ts +22 -0
- package/components/EzOutlet.ts +181 -0
- package/components/HtmlWrapper.ts +305 -0
- package/components/avatar/EzAvatar.module.scss +200 -0
- package/components/avatar/EzAvatar.ts +130 -0
- package/components/badge/EzBadge.module.scss +202 -0
- package/components/badge/EzBadge.ts +77 -0
- package/components/button/EzButton.module.scss +402 -0
- package/components/button/EzButton.ts +175 -0
- package/components/button/EzButtonGroup.ts +48 -0
- package/components/card/EzCard.module.scss +71 -0
- package/components/card/EzCard.ts +120 -0
- package/components/chart/EzBarChart.ts +47 -0
- package/components/chart/EzChart.module.scss +14 -0
- package/components/chart/EzChart.ts +279 -0
- package/components/chart/EzDoughnutChart.ts +47 -0
- package/components/chart/EzLineChart.ts +53 -0
- package/components/checkbox/EzCheckbox.module.scss +145 -0
- package/components/checkbox/EzCheckbox.ts +115 -0
- package/components/dataview/EzDataView.module.scss +115 -0
- package/components/dataview/EzDataView.ts +355 -0
- package/components/dataview/modes/EzDataViewCards.ts +322 -0
- package/components/dataview/modes/EzDataViewGrid.ts +76 -0
- package/components/datepicker/EzDatePicker.module.scss +348 -0
- package/components/datepicker/EzDatePicker.ts +519 -0
- package/components/dialog/EzDialog.module.scss +180 -0
- package/components/dropdown/EzDropdown.module.scss +107 -0
- package/components/dropdown/EzDropdown.ts +235 -0
- package/components/feed/EzActivityFeed.module.scss +90 -0
- package/components/feed/EzActivityFeed.ts +78 -0
- package/components/form/EzForm.ts +364 -0
- package/components/form/EzValidators.test.js +421 -0
- package/components/form/EzValidators.ts +202 -0
- package/components/grid/EzGrid.scss +88 -0
- package/components/grid/EzGrid.ts +1085 -0
- package/components/grid/EzGridContainer.ts +104 -0
- package/components/grid/body/EzGridBody.scss +283 -0
- package/components/grid/body/EzGridBody.ts +549 -0
- package/components/grid/body/EzGridCell.ts +211 -0
- package/components/grid/body/EzGridRow.ts +196 -0
- package/components/grid/filter/EzGridFilters.scss +78 -0
- package/components/grid/filter/EzGridFilters.ts +285 -0
- package/components/grid/footer/EzGridFooter.scss +136 -0
- package/components/grid/footer/EzGridFooter.ts +448 -0
- package/components/grid/header/EzGridHeader.scss +199 -0
- package/components/grid/header/EzGridHeader.ts +430 -0
- package/components/grid/query/EzGridQuery.ts +81 -0
- package/components/grid/state/EzGridColumns.ts +155 -0
- package/components/grid/state/EzGridController.ts +470 -0
- package/components/grid/state/EzGridLifecycle.ts +136 -0
- package/components/grid/state/EzGridNormalizers.test.js +273 -0
- package/components/grid/state/EzGridNormalizers.ts +162 -0
- package/components/grid/state/EzGridParts.ts +233 -0
- package/components/grid/state/EzGridPersistence.ts +140 -0
- package/components/grid/state/EzGridRemote.test.js +573 -0
- package/components/grid/state/EzGridRemote.ts +335 -0
- package/components/grid/state/EzGridSelection.ts +231 -0
- package/components/grid/state/EzGridSort.ts +286 -0
- package/components/grid/title/EzGridActionBar.ts +98 -0
- package/components/grid/title/EzGridTitle.ts +114 -0
- package/components/grid/title/EzGridTitleBar.scss +65 -0
- package/components/grid/title/EzGridTitleBar.ts +87 -0
- package/components/grid/types.ts +607 -0
- package/components/panel/EzPanel.module.scss +133 -0
- package/components/panel/EzPanel.ts +147 -0
- package/components/radio/EzRadio.module.scss +190 -0
- package/components/radio/EzRadio.ts +149 -0
- package/components/select/EzSelect.module.scss +153 -0
- package/components/select/EzSelect.ts +238 -0
- package/components/skeleton/EzSkeleton.module.scss +95 -0
- package/components/skeleton/EzSkeleton.ts +70 -0
- package/components/store/EzStore.ts +344 -0
- package/components/switch/EzSwitch.module.scss +164 -0
- package/components/switch/EzSwitch.ts +117 -0
- package/components/tabs/EzTabPanel.module.scss +181 -0
- package/components/tabs/EzTabPanel.ts +402 -0
- package/components/textarea/EzTextarea.module.scss +131 -0
- package/components/textarea/EzTextarea.ts +161 -0
- package/components/timepicker/EzTimePicker.module.scss +282 -0
- package/components/timepicker/EzTimePicker.ts +540 -0
- package/components/toast/EzToast.module.scss +291 -0
- package/components/tooltip/EzTooltip.module.scss +124 -0
- package/components/tooltip/EzTooltip.ts +153 -0
- package/core/EzComponentTypes.ts +693 -0
- package/core/EzError.ts +63 -0
- package/core/EzModel.ts +268 -0
- package/core/EzTypes.ts +328 -0
- package/core/eventBus.ts +284 -0
- package/core/ez.ts +617 -0
- package/core/loader.ts +725 -0
- package/core/renderer.ts +1010 -0
- package/core/router.ts +490 -0
- package/core/services.ts +124 -0
- package/core/state.ts +142 -0
- package/core/utils.ts +81 -0
- package/package.json +51 -0
- package/services/RouteUI.js +17 -0
- package/services/crypto.js +64 -0
- package/services/dialog.js +222 -0
- package/services/fetchApi.js +63 -0
- package/services/firebase.js +30 -0
- package/services/toast.js +214 -0
- package/template/doc/EzDocs.js +15 -0
- package/template/doc/EzDocs.module.scss +627 -0
- package/template/doc/EzDocsController.js +164 -0
- package/template/doc/data/activityfeed/EzActivityFeedDoc.js +42 -0
- package/template/doc/data/avatar/EzAvatarDoc.js +71 -0
- package/template/doc/data/badge/EzBadgeDoc.js +92 -0
- package/template/doc/data/button/EzButtonDoc.js +77 -0
- package/template/doc/data/buttongroup/EzButtonGroupDoc.js +102 -0
- package/template/doc/data/card/EzCardDoc.js +39 -0
- package/template/doc/data/chart/EzChartDoc.js +60 -0
- package/template/doc/data/checkbox/EzCheckboxDoc.js +67 -0
- package/template/doc/data/component/EzComponentDoc.js +34 -0
- package/template/doc/data/cssmodules/CSSModulesDoc.js +70 -0
- package/template/doc/data/datepicker/EzDatePickerDoc.js +126 -0
- package/template/doc/data/dialog/EzDialogDoc.js +217 -0
- package/template/doc/data/dropdown/EzDropdownDoc.js +178 -0
- package/template/doc/data/form/EzFormDoc.js +90 -0
- package/template/doc/data/grid/EzGridDoc.js +99 -0
- package/template/doc/data/input/EzInputDoc.js +92 -0
- package/template/doc/data/label/EzLabelDoc.js +40 -0
- package/template/doc/data/model/EzModelDoc.js +53 -0
- package/template/doc/data/outlet/EzOutletDoc.js +63 -0
- package/template/doc/data/panel/EzPanelDoc.js +214 -0
- package/template/doc/data/radio/EzRadioDoc.js +174 -0
- package/template/doc/data/router/EzRouterDoc.js +75 -0
- package/template/doc/data/select/EzSelectDoc.js +37 -0
- package/template/doc/data/skeleton/EzSkeletonDoc.js +149 -0
- package/template/doc/data/switch/EzSwitchDoc.js +82 -0
- package/template/doc/data/tabpanel/EzTabPanelDoc.js +44 -0
- package/template/doc/data/textarea/EzTextareaDoc.js +131 -0
- package/template/doc/data/timepicker/EzTimePickerDoc.js +107 -0
- package/template/doc/data/tooltip/EzTooltipDoc.js +193 -0
- package/template/doc/data/validators/EzValidatorsDoc.js +37 -0
- package/template/doc/sidebar/EzDocsSidebar.js +32 -0
- package/template/doc/sidebar/category/EzDocsCategory.js +33 -0
- package/template/doc/sidebar/item/EzDocsComponentItem.js +24 -0
- package/template/doc/viewer/EzDocsViewer.js +18 -0
- package/template/doc/viewer/codepanel/EzDocsCodePanel.js +51 -0
- package/template/doc/viewer/content/EzDocsContent.js +315 -0
- package/template/doc/viewer/header/EzDocsViewerHeader.js +46 -0
- package/template/doc/viewer/showcase/EzDocsShowcase.js +59 -0
- package/template/doc/viewer/showcase/EzDocsShowcaseSection.js +25 -0
- package/template/doc/viewer/showcase/EzDocsVariantItem.js +29 -0
- package/template/doc/welcome/EzDocsWelcome.js +48 -0
- package/themes/ez-theme.scss +179 -0
- package/themes/nature-fresh.scss +169 -0
- package/types/global.d.ts +21 -0
- package/utils/cssModules.js +81 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// EzGrid/state/EzGridPersistence.ts
|
|
2
|
+
|
|
3
|
+
import { EzError } from '../../../core/EzError.js';
|
|
4
|
+
import type {
|
|
5
|
+
ColumnsSnapshot,
|
|
6
|
+
Sorter,
|
|
7
|
+
SelectionSnapshot,
|
|
8
|
+
GridStateSnapshot
|
|
9
|
+
} from '../types.js';
|
|
10
|
+
|
|
11
|
+
declare const ez: {
|
|
12
|
+
_gridStateRegistry: Record<string, GridRegistryEntry>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export interface GridRegistryEntry {
|
|
16
|
+
_columnState?: ColumnsSnapshot | null;
|
|
17
|
+
_sortState?: Sorter[] | null;
|
|
18
|
+
_selectionState?: SelectionSnapshot | null;
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface EzGridPersistenceRef {
|
|
23
|
+
_context?: {
|
|
24
|
+
route?: string;
|
|
25
|
+
view?: string;
|
|
26
|
+
};
|
|
27
|
+
config: {
|
|
28
|
+
id?: string;
|
|
29
|
+
};
|
|
30
|
+
statefulPersist?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Re-export for backwards compatibility
|
|
34
|
+
export type { GridStateSnapshot } from '../types.js';
|
|
35
|
+
|
|
36
|
+
export class EzGridPersistence {
|
|
37
|
+
grid: EzGridPersistenceRef;
|
|
38
|
+
|
|
39
|
+
constructor(grid: EzGridPersistenceRef) {
|
|
40
|
+
this.grid = grid;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ==========================================================
|
|
44
|
+
// State Key Management
|
|
45
|
+
// ==========================================================
|
|
46
|
+
|
|
47
|
+
getStateKey(): string {
|
|
48
|
+
const route = this.grid._context?.route ?? 'global';
|
|
49
|
+
const view = this.grid._context?.view ?? 'unknown';
|
|
50
|
+
const id = this.grid.config.id;
|
|
51
|
+
|
|
52
|
+
if (!id) {
|
|
53
|
+
throw new EzError({
|
|
54
|
+
code: 'EZ_GRID_STATE_001',
|
|
55
|
+
message: 'Stateful EzGrid requires a unique "id"'
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return `${route}:${view}:${id}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
getStorageKey(): string {
|
|
63
|
+
return `EZ_GRID_STATE::${this.getStateKey()}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ==========================================================
|
|
67
|
+
// Storage Operations
|
|
68
|
+
// ==========================================================
|
|
69
|
+
|
|
70
|
+
load(): GridStateSnapshot | null {
|
|
71
|
+
if (!this.grid.statefulPersist) return null;
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const raw = localStorage.getItem(this.getStorageKey());
|
|
75
|
+
if (!raw) return null;
|
|
76
|
+
return JSON.parse(raw) as GridStateSnapshot;
|
|
77
|
+
} catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
save(): void {
|
|
83
|
+
if (!this.grid.statefulPersist) return;
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
const entry = ez._gridStateRegistry[this.getStateKey()];
|
|
87
|
+
if (!entry) return;
|
|
88
|
+
|
|
89
|
+
const snapshot: GridStateSnapshot = {
|
|
90
|
+
columnState: entry._columnState ?? null,
|
|
91
|
+
sortState: entry._sortState ?? null,
|
|
92
|
+
selectionState: entry._selectionState ?? null
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
localStorage.setItem(
|
|
96
|
+
this.getStorageKey(),
|
|
97
|
+
JSON.stringify(snapshot)
|
|
98
|
+
);
|
|
99
|
+
} catch {
|
|
100
|
+
// fail silently
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ==========================================================
|
|
105
|
+
// Registry Operations
|
|
106
|
+
// ==========================================================
|
|
107
|
+
|
|
108
|
+
getRegistryEntry(): GridRegistryEntry | undefined {
|
|
109
|
+
return ez._gridStateRegistry[this.getStateKey()];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
setRegistryEntry(controller: GridRegistryEntry): void {
|
|
113
|
+
ez._gridStateRegistry[this.getStateKey()] = controller;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
hasRegistryEntry(): boolean {
|
|
117
|
+
return !!ez._gridStateRegistry[this.getStateKey()];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
updateColumnState(snapshot: ColumnsSnapshot | null): void {
|
|
121
|
+
const entry = this.getRegistryEntry();
|
|
122
|
+
if (entry) {
|
|
123
|
+
entry._columnState = snapshot;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
updateSortState(snapshot: Sorter[] | null): void {
|
|
128
|
+
const entry = this.getRegistryEntry();
|
|
129
|
+
if (entry) {
|
|
130
|
+
entry._sortState = snapshot;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
updateSelectionState(snapshot: SelectionSnapshot | null): void {
|
|
135
|
+
const entry = this.getRegistryEntry();
|
|
136
|
+
if (entry) {
|
|
137
|
+
entry._selectionState = snapshot;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
// ==========================================================
|
|
2
|
+
// EzGridRemote - Unit Tests
|
|
3
|
+
// ==========================================================
|
|
4
|
+
// Para correr: npm test EzGridRemote
|
|
5
|
+
// ==========================================================
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
8
|
+
import { EzGridRemote } from './EzGridRemote.js';
|
|
9
|
+
|
|
10
|
+
// ==========================================================
|
|
11
|
+
// Mock Setup
|
|
12
|
+
// ==========================================================
|
|
13
|
+
|
|
14
|
+
// Mock del objeto global ez
|
|
15
|
+
const mockEz = {
|
|
16
|
+
_api: {
|
|
17
|
+
request: vi.fn()
|
|
18
|
+
},
|
|
19
|
+
getController: vi.fn()
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Instalar mock global antes de cada test
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
globalThis.ez = mockEz;
|
|
25
|
+
vi.clearAllMocks();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
delete globalThis.ez;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Helper para crear mock de grid
|
|
33
|
+
function createMockGrid(options = {}) {
|
|
34
|
+
return {
|
|
35
|
+
controller: {
|
|
36
|
+
state: {
|
|
37
|
+
page: 1,
|
|
38
|
+
pageSize: 25
|
|
39
|
+
},
|
|
40
|
+
setLoading: vi.fn(),
|
|
41
|
+
setError: vi.fn(),
|
|
42
|
+
_lastSortSnapshot: null,
|
|
43
|
+
_lastFilterSnapshot: null
|
|
44
|
+
},
|
|
45
|
+
config: {
|
|
46
|
+
controller: options.controllerName || 'TestController'
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ==========================================================
|
|
52
|
+
// Constructor & Config
|
|
53
|
+
// ==========================================================
|
|
54
|
+
|
|
55
|
+
describe('EzGridRemote - Configuration', () => {
|
|
56
|
+
|
|
57
|
+
it('should normalize string api to object', () => {
|
|
58
|
+
const grid = createMockGrid();
|
|
59
|
+
const remote = new EzGridRemote(grid, {
|
|
60
|
+
api: 'v1/users'
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
expect(remote.config.api.read).toBe('v1/users');
|
|
64
|
+
expect(remote.config.api.create).toBe('v1/users');
|
|
65
|
+
expect(remote.config.api.update).toBe('v1/users');
|
|
66
|
+
expect(remote.config.api.delete).toBe('v1/users');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should preserve object api', () => {
|
|
70
|
+
const grid = createMockGrid();
|
|
71
|
+
const remote = new EzGridRemote(grid, {
|
|
72
|
+
api: {
|
|
73
|
+
read: 'v1/users',
|
|
74
|
+
create: 'v1/users/create'
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
expect(remote.config.api.read).toBe('v1/users');
|
|
79
|
+
expect(remote.config.api.create).toBe('v1/users/create');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should use default source paths', () => {
|
|
83
|
+
const grid = createMockGrid();
|
|
84
|
+
const remote = new EzGridRemote(grid, {
|
|
85
|
+
api: 'v1/users'
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
expect(remote.config.source.dataPath).toBe('data');
|
|
89
|
+
expect(remote.config.source.countPath).toBe('count');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should use custom source paths', () => {
|
|
93
|
+
const grid = createMockGrid();
|
|
94
|
+
const remote = new EzGridRemote(grid, {
|
|
95
|
+
api: 'v1/users',
|
|
96
|
+
source: {
|
|
97
|
+
dataPath: 'result.items',
|
|
98
|
+
countPath: 'result.total'
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
expect(remote.config.source.dataPath).toBe('result.items');
|
|
103
|
+
expect(remote.config.source.countPath).toBe('result.total');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should default filter mode to onInput', () => {
|
|
107
|
+
const grid = createMockGrid();
|
|
108
|
+
const remote = new EzGridRemote(grid, {
|
|
109
|
+
api: 'v1/users'
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
expect(remote.config.filter).toBe('onInput');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should preserve filter mode onEnter', () => {
|
|
116
|
+
const grid = createMockGrid();
|
|
117
|
+
const remote = new EzGridRemote(grid, {
|
|
118
|
+
api: 'v1/users',
|
|
119
|
+
filter: 'onEnter'
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
expect(remote.config.filter).toBe('onEnter');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// ==========================================================
|
|
128
|
+
// isEnabled
|
|
129
|
+
// ==========================================================
|
|
130
|
+
|
|
131
|
+
describe('EzGridRemote.isEnabled', () => {
|
|
132
|
+
|
|
133
|
+
it('should return true when read api is configured', () => {
|
|
134
|
+
const grid = createMockGrid();
|
|
135
|
+
const remote = new EzGridRemote(grid, {
|
|
136
|
+
api: 'v1/users'
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
expect(remote.isEnabled()).toBe(true);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should return false when no config', () => {
|
|
143
|
+
const grid = createMockGrid();
|
|
144
|
+
const remote = new EzGridRemote(grid, null);
|
|
145
|
+
|
|
146
|
+
expect(remote.isEnabled()).toBe(false);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should return false when no read api', () => {
|
|
150
|
+
const grid = createMockGrid();
|
|
151
|
+
const remote = new EzGridRemote(grid, {
|
|
152
|
+
api: {}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
expect(remote.isEnabled()).toBe(false);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// ==========================================================
|
|
161
|
+
// URL Building
|
|
162
|
+
// ==========================================================
|
|
163
|
+
|
|
164
|
+
describe('EzGridRemote._buildReadUrl', () => {
|
|
165
|
+
|
|
166
|
+
it('should build basic URL with pagination', () => {
|
|
167
|
+
const grid = createMockGrid();
|
|
168
|
+
const remote = new EzGridRemote(grid, {
|
|
169
|
+
api: 'v1/users'
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const url = remote._buildReadUrl({ page: 1, pageSize: 25 });
|
|
173
|
+
|
|
174
|
+
expect(url).toBe('v1/users?page=1&pageSize=25');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('should include sort params', () => {
|
|
178
|
+
const grid = createMockGrid();
|
|
179
|
+
const remote = new EzGridRemote(grid, {
|
|
180
|
+
api: 'v1/users'
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const url = remote._buildReadUrl({
|
|
184
|
+
page: 1,
|
|
185
|
+
pageSize: 25,
|
|
186
|
+
sort: [{ property: 'name', direction: 'ASC' }]
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
expect(url).toContain('sortBy=name');
|
|
190
|
+
expect(url).toContain('sortDir=ASC');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should include filters as JSON', () => {
|
|
194
|
+
const grid = createMockGrid();
|
|
195
|
+
const remote = new EzGridRemote(grid, {
|
|
196
|
+
api: 'v1/users'
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const filters = [{ field: 'name', operator: 'LIKE', value: 'John' }];
|
|
200
|
+
const url = remote._buildReadUrl({
|
|
201
|
+
page: 1,
|
|
202
|
+
pageSize: 25,
|
|
203
|
+
filters
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
expect(url).toContain('filters=');
|
|
207
|
+
expect(url).toContain(encodeURIComponent(JSON.stringify(filters)));
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// ==========================================================
|
|
213
|
+
// Response Parsing
|
|
214
|
+
// ==========================================================
|
|
215
|
+
|
|
216
|
+
describe('EzGridRemote._parseResponse', () => {
|
|
217
|
+
|
|
218
|
+
it('should parse data and count from default paths', () => {
|
|
219
|
+
const grid = createMockGrid();
|
|
220
|
+
const remote = new EzGridRemote(grid, {
|
|
221
|
+
api: 'v1/users'
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const response = {
|
|
225
|
+
data: [{ id: 1 }, { id: 2 }],
|
|
226
|
+
count: 100
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const result = remote._parseResponse(response);
|
|
230
|
+
|
|
231
|
+
expect(result.data).toEqual([{ id: 1 }, { id: 2 }]);
|
|
232
|
+
expect(result.total).toBe(100);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('should parse data from nested path', () => {
|
|
236
|
+
const grid = createMockGrid();
|
|
237
|
+
const remote = new EzGridRemote(grid, {
|
|
238
|
+
api: 'v1/users',
|
|
239
|
+
source: {
|
|
240
|
+
dataPath: 'result.items',
|
|
241
|
+
countPath: 'result.total'
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
const response = {
|
|
246
|
+
result: {
|
|
247
|
+
items: [{ id: 1 }],
|
|
248
|
+
total: 50
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const result = remote._parseResponse(response);
|
|
253
|
+
|
|
254
|
+
expect(result.data).toEqual([{ id: 1 }]);
|
|
255
|
+
expect(result.total).toBe(50);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('should return empty array if data is not array', () => {
|
|
259
|
+
const grid = createMockGrid();
|
|
260
|
+
const remote = new EzGridRemote(grid, {
|
|
261
|
+
api: 'v1/users'
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
const result = remote._parseResponse({ data: null });
|
|
265
|
+
|
|
266
|
+
expect(result.data).toEqual([]);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('should use data length if count not available', () => {
|
|
270
|
+
const grid = createMockGrid();
|
|
271
|
+
const remote = new EzGridRemote(grid, {
|
|
272
|
+
api: 'v1/users'
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
const result = remote._parseResponse({
|
|
276
|
+
data: [{ id: 1 }, { id: 2 }, { id: 3 }]
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
expect(result.total).toBe(3);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// ==========================================================
|
|
285
|
+
// Callback Resolution
|
|
286
|
+
// ==========================================================
|
|
287
|
+
|
|
288
|
+
describe('EzGridRemote._resolveCallback', () => {
|
|
289
|
+
|
|
290
|
+
it('should return function as-is', () => {
|
|
291
|
+
const grid = createMockGrid();
|
|
292
|
+
const remote = new EzGridRemote(grid, { api: 'v1/users' });
|
|
293
|
+
|
|
294
|
+
const fn = () => 'test';
|
|
295
|
+
const result = remote._resolveCallback(fn);
|
|
296
|
+
|
|
297
|
+
expect(result).toBe(fn);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('should resolve ControllerName:functionName format', () => {
|
|
301
|
+
const mockController = {
|
|
302
|
+
handleData: vi.fn().mockReturnValue('handled')
|
|
303
|
+
};
|
|
304
|
+
mockEz.getController.mockReturnValue(mockController);
|
|
305
|
+
|
|
306
|
+
const grid = createMockGrid();
|
|
307
|
+
const remote = new EzGridRemote(grid, { api: 'v1/users' });
|
|
308
|
+
|
|
309
|
+
const result = remote._resolveCallback('UserController:handleData');
|
|
310
|
+
|
|
311
|
+
expect(mockEz.getController).toHaveBeenCalledWith('UserController');
|
|
312
|
+
expect(typeof result).toBe('function');
|
|
313
|
+
expect(result()).toBe('handled');
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('should resolve functionName using config.controller', () => {
|
|
317
|
+
const mockController = {
|
|
318
|
+
onLoad: vi.fn().mockReturnValue('loaded')
|
|
319
|
+
};
|
|
320
|
+
mockEz.getController.mockReturnValue(mockController);
|
|
321
|
+
|
|
322
|
+
const grid = createMockGrid({ controllerName: 'MyController' });
|
|
323
|
+
const remote = new EzGridRemote(grid, { api: 'v1/users' });
|
|
324
|
+
|
|
325
|
+
const result = remote._resolveCallback('onLoad');
|
|
326
|
+
|
|
327
|
+
expect(mockEz.getController).toHaveBeenCalledWith('MyController');
|
|
328
|
+
expect(typeof result).toBe('function');
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('should return null for invalid string reference', () => {
|
|
332
|
+
mockEz.getController.mockReturnValue(null);
|
|
333
|
+
|
|
334
|
+
const grid = createMockGrid();
|
|
335
|
+
const remote = new EzGridRemote(grid, { api: 'v1/users' });
|
|
336
|
+
|
|
337
|
+
const result = remote._resolveCallback('nonExistent');
|
|
338
|
+
|
|
339
|
+
expect(result).toBe(null);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it('should return null for non-function/non-string', () => {
|
|
343
|
+
const grid = createMockGrid();
|
|
344
|
+
const remote = new EzGridRemote(grid, { api: 'v1/users' });
|
|
345
|
+
|
|
346
|
+
expect(remote._resolveCallback(123)).toBe(null);
|
|
347
|
+
expect(remote._resolveCallback(null)).toBe(null);
|
|
348
|
+
expect(remote._resolveCallback({})).toBe(null);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
// ==========================================================
|
|
354
|
+
// Load
|
|
355
|
+
// ==========================================================
|
|
356
|
+
|
|
357
|
+
describe('EzGridRemote.load', () => {
|
|
358
|
+
|
|
359
|
+
it('should call ez._api.request with correct URL', async () => {
|
|
360
|
+
mockEz._api.request.mockResolvedValue({
|
|
361
|
+
data: [{ id: 1 }],
|
|
362
|
+
count: 1
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
const grid = createMockGrid();
|
|
366
|
+
const remote = new EzGridRemote(grid, {
|
|
367
|
+
api: 'v1/users'
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
await remote.load();
|
|
371
|
+
|
|
372
|
+
expect(mockEz._api.request).toHaveBeenCalledWith(
|
|
373
|
+
expect.stringContaining('v1/users')
|
|
374
|
+
);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it('should return parsed data and total', async () => {
|
|
378
|
+
mockEz._api.request.mockResolvedValue({
|
|
379
|
+
data: [{ id: 1 }, { id: 2 }],
|
|
380
|
+
count: 100
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
const grid = createMockGrid();
|
|
384
|
+
const remote = new EzGridRemote(grid, {
|
|
385
|
+
api: 'v1/users'
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
const result = await remote.load();
|
|
389
|
+
|
|
390
|
+
expect(result.data).toHaveLength(2);
|
|
391
|
+
expect(result.total).toBe(100);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it('should set loading state', async () => {
|
|
395
|
+
mockEz._api.request.mockResolvedValue({ data: [], count: 0 });
|
|
396
|
+
|
|
397
|
+
const grid = createMockGrid();
|
|
398
|
+
const remote = new EzGridRemote(grid, {
|
|
399
|
+
api: 'v1/users'
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
await remote.load();
|
|
403
|
+
|
|
404
|
+
expect(grid.controller.setLoading).toHaveBeenCalledWith(true);
|
|
405
|
+
expect(grid.controller.setLoading).toHaveBeenCalledWith(false);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it('should call beforeLoad hook', async () => {
|
|
409
|
+
mockEz._api.request.mockResolvedValue({ data: [], count: 0 });
|
|
410
|
+
|
|
411
|
+
const beforeLoad = vi.fn();
|
|
412
|
+
const grid = createMockGrid();
|
|
413
|
+
const remote = new EzGridRemote(grid, {
|
|
414
|
+
api: 'v1/users',
|
|
415
|
+
listenTo: { beforeLoad }
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
await remote.load();
|
|
419
|
+
|
|
420
|
+
expect(beforeLoad).toHaveBeenCalledWith(
|
|
421
|
+
expect.objectContaining({ page: 1, pageSize: 25 }),
|
|
422
|
+
expect.objectContaining({ grid, remote })
|
|
423
|
+
);
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it('should call afterLoad hook with result', async () => {
|
|
427
|
+
const responseData = { data: [{ id: 1 }], count: 1 };
|
|
428
|
+
mockEz._api.request.mockResolvedValue(responseData);
|
|
429
|
+
|
|
430
|
+
const afterLoad = vi.fn();
|
|
431
|
+
const grid = createMockGrid();
|
|
432
|
+
const remote = new EzGridRemote(grid, {
|
|
433
|
+
api: 'v1/users',
|
|
434
|
+
listenTo: { afterLoad }
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
await remote.load();
|
|
438
|
+
|
|
439
|
+
expect(afterLoad).toHaveBeenCalledWith(
|
|
440
|
+
expect.objectContaining({ data: [{ id: 1 }], total: 1 }),
|
|
441
|
+
expect.objectContaining({ grid, remote, response: responseData })
|
|
442
|
+
);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it('should call onError hook on failure', async () => {
|
|
446
|
+
const error = new Error('Network error');
|
|
447
|
+
mockEz._api.request.mockRejectedValue(error);
|
|
448
|
+
|
|
449
|
+
const onError = vi.fn();
|
|
450
|
+
const grid = createMockGrid();
|
|
451
|
+
const remote = new EzGridRemote(grid, {
|
|
452
|
+
api: 'v1/users',
|
|
453
|
+
listenTo: { onError }
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
await remote.load();
|
|
457
|
+
|
|
458
|
+
expect(onError).toHaveBeenCalledWith(
|
|
459
|
+
error,
|
|
460
|
+
expect.objectContaining({ grid, remote })
|
|
461
|
+
);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it('should return empty data on error', async () => {
|
|
465
|
+
mockEz._api.request.mockRejectedValue(new Error('Failed'));
|
|
466
|
+
|
|
467
|
+
const grid = createMockGrid();
|
|
468
|
+
const remote = new EzGridRemote(grid, {
|
|
469
|
+
api: 'v1/users'
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
const result = await remote.load();
|
|
473
|
+
|
|
474
|
+
expect(result.data).toEqual([]);
|
|
475
|
+
expect(result.total).toBe(0);
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
it('should prevent concurrent loads', async () => {
|
|
479
|
+
let resolveFirst;
|
|
480
|
+
mockEz._api.request.mockImplementationOnce(() =>
|
|
481
|
+
new Promise(resolve => { resolveFirst = resolve; })
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
const grid = createMockGrid();
|
|
485
|
+
const remote = new EzGridRemote(grid, {
|
|
486
|
+
api: 'v1/users'
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
// Start first load
|
|
490
|
+
const firstLoad = remote.load();
|
|
491
|
+
|
|
492
|
+
// Try second load while first is pending
|
|
493
|
+
const secondResult = await remote.load();
|
|
494
|
+
|
|
495
|
+
// Second should return empty immediately
|
|
496
|
+
expect(secondResult.data).toEqual([]);
|
|
497
|
+
|
|
498
|
+
// Complete first load
|
|
499
|
+
resolveFirst({ data: [{ id: 1 }], count: 1 });
|
|
500
|
+
await firstLoad;
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
// ==========================================================
|
|
506
|
+
// CRUD Operations
|
|
507
|
+
// ==========================================================
|
|
508
|
+
|
|
509
|
+
describe('EzGridRemote CRUD', () => {
|
|
510
|
+
|
|
511
|
+
describe('create', () => {
|
|
512
|
+
it('should POST to create endpoint', async () => {
|
|
513
|
+
mockEz._api.request.mockResolvedValue({ id: 1 });
|
|
514
|
+
|
|
515
|
+
const grid = createMockGrid();
|
|
516
|
+
const remote = new EzGridRemote(grid, {
|
|
517
|
+
api: { read: 'v1/users', create: 'v1/users' }
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
await remote.create({ name: 'John' });
|
|
521
|
+
|
|
522
|
+
expect(mockEz._api.request).toHaveBeenCalledWith('v1/users', {
|
|
523
|
+
method: 'POST',
|
|
524
|
+
body: { name: 'John' }
|
|
525
|
+
});
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
it('should throw if create not configured', async () => {
|
|
529
|
+
const grid = createMockGrid();
|
|
530
|
+
const remote = new EzGridRemote(grid, {
|
|
531
|
+
api: { read: 'v1/users' }
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
await expect(remote.create({})).rejects.toThrow('Create endpoint not configured');
|
|
535
|
+
});
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
describe('update', () => {
|
|
539
|
+
it('should PUT to update endpoint with id', async () => {
|
|
540
|
+
mockEz._api.request.mockResolvedValue({ id: 1 });
|
|
541
|
+
|
|
542
|
+
const grid = createMockGrid();
|
|
543
|
+
const remote = new EzGridRemote(grid, {
|
|
544
|
+
api: { read: 'v1/users', update: 'v1/users' }
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
await remote.update(1, { name: 'Jane' });
|
|
548
|
+
|
|
549
|
+
expect(mockEz._api.request).toHaveBeenCalledWith('v1/users/1', {
|
|
550
|
+
method: 'PUT',
|
|
551
|
+
body: { name: 'Jane' }
|
|
552
|
+
});
|
|
553
|
+
});
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
describe('delete', () => {
|
|
557
|
+
it('should DELETE to endpoint with id', async () => {
|
|
558
|
+
mockEz._api.request.mockResolvedValue({});
|
|
559
|
+
|
|
560
|
+
const grid = createMockGrid();
|
|
561
|
+
const remote = new EzGridRemote(grid, {
|
|
562
|
+
api: { read: 'v1/users', delete: 'v1/users' }
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
await remote.delete(1);
|
|
566
|
+
|
|
567
|
+
expect(mockEz._api.request).toHaveBeenCalledWith('v1/users/1', {
|
|
568
|
+
method: 'DELETE'
|
|
569
|
+
});
|
|
570
|
+
});
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
});
|