pict-section-recordset 1.0.70 → 1.2.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 (69) hide show
  1. package/package.json +5 -1
  2. package/source/providers/Filter-Data-Provider.js +16 -5
  3. package/source/views/Filter-PersistenceView.js +150 -35
  4. package/source/views/RecordSet-Filters.js +230 -28
  5. package/source/views/filters/RecordSet-Filter-Base.js +86 -8
  6. package/source/views/read/RecordSet-Read.js +308 -2
  7. package/types/providers/Filter-Data-Provider.d.ts +1 -1
  8. package/types/providers/Filter-Data-Provider.d.ts.map +1 -1
  9. package/types/views/Filter-PersistenceView.d.ts +23 -2
  10. package/types/views/Filter-PersistenceView.d.ts.map +1 -1
  11. package/types/views/RecordSet-Filters.d.ts +26 -1
  12. package/types/views/RecordSet-Filters.d.ts.map +1 -1
  13. package/types/views/filters/RecordSet-Filter-Base.d.ts +14 -0
  14. package/types/views/filters/RecordSet-Filter-Base.d.ts.map +1 -1
  15. package/types/views/list/RecordSet-List.d.ts.map +1 -1
  16. package/types/views/read/RecordSet-Read.d.ts +51 -0
  17. package/types/views/read/RecordSet-Read.d.ts.map +1 -1
  18. package/.vscode/launch.json +0 -46
  19. package/CONTRIBUTING.md +0 -50
  20. package/debug/Harness.js +0 -0
  21. package/docs/.nojekyll +0 -0
  22. package/docs/README.md +0 -76
  23. package/docs/_brand.json +0 -18
  24. package/docs/_cover.md +0 -11
  25. package/docs/_sidebar.md +0 -19
  26. package/docs/_version.json +0 -7
  27. package/docs/api-reference.md +0 -233
  28. package/docs/filters.md +0 -151
  29. package/docs/index.html +0 -38
  30. package/docs/record-providers.md +0 -155
  31. package/docs/retold-catalog.json +0 -87
  32. package/docs/retold-keyword-index.json +0 -5227
  33. package/docs/views/create/README.md +0 -181
  34. package/docs/views/dashboard/README.md +0 -308
  35. package/docs/views/list/README.md +0 -260
  36. package/docs/views/read/README.md +0 -216
  37. package/eslint.config.mjs +0 -10
  38. package/example_applications/README.md +0 -39
  39. package/example_applications/ServeExamples.js +0 -82
  40. package/example_applications/bookstore/.quackage.json +0 -9
  41. package/example_applications/bookstore/Bookstore-Application-Configuration.json +0 -4
  42. package/example_applications/bookstore/Bookstore-Application.js +0 -671
  43. package/example_applications/bookstore/css/bookstore.css +0 -729
  44. package/example_applications/bookstore/css/pure.min.css +0 -11
  45. package/example_applications/bookstore/html/index.html +0 -46
  46. package/example_applications/bookstore/package.json +0 -34
  47. package/example_applications/bookstore/providers/PictRouter-Bookstore.json +0 -32
  48. package/example_applications/bookstore/views/PictView-Bookstore-Content-About.json +0 -21
  49. package/example_applications/bookstore/views/PictView-Bookstore-Content-Legal.json +0 -21
  50. package/example_applications/bookstore/views/PictView-Bookstore-Dashboard.js +0 -147
  51. package/example_applications/bookstore/views/PictView-Bookstore-Layout.js +0 -85
  52. package/example_applications/bookstore/views/PictView-Bookstore-Login.js +0 -58
  53. package/example_applications/bookstore/views/PictView-Bookstore-Navigation.js +0 -228
  54. package/example_applications/index.html +0 -50
  55. package/example_applications/mocks/book-edit-view.html +0 -173
  56. package/example_applications/mocks/book-read-view.html +0 -166
  57. package/example_applications/mocks/list-view.html +0 -185
  58. package/example_applications/package.json +0 -16
  59. package/example_applications/simple_entity/.quackage.json +0 -9
  60. package/example_applications/simple_entity/README-Simple-RecordSet.md +0 -8
  61. package/example_applications/simple_entity/Simple-RecordSet-Application.js +0 -887
  62. package/example_applications/simple_entity/html/index.html +0 -207
  63. package/example_applications/simple_entity/package.json +0 -27
  64. package/test/PictSectionRecordSet-Basic_tests.js +0 -205
  65. package/test/PictSectionRecordSet-Filter-Data-Provider_tests.js +0 -263
  66. package/test/PictSectionRecordSet-Filter-InstanceViews-Render_tests.js +0 -328
  67. package/test/PictSectionRecordSet-RecordProvider-Meadow_tests.js +0 -216
  68. package/tsconfig.build.json +0 -16
  69. package/tsconfig.json +0 -16
@@ -1,263 +0,0 @@
1
- /*
2
- Unit tests for PictSectionRecordSet Basic
3
-
4
- */
5
-
6
- const libBrowserEnv = require('browser-env');
7
-
8
- const libPictView = require('pict-view');
9
-
10
- const sinon = require('sinon');
11
- const Chai = require('chai');
12
- const Expect = Chai.expect;
13
-
14
- const libPict = require('pict');
15
-
16
- const libPictSectionRecordSet = require('../source/Pict-Section-RecordSet.js');
17
- const libPictSectionRecordSetFilterDataProvider = require('../source/providers/Filter-Data-Provider.js');
18
-
19
- class DoNothingApplication extends libPictSectionRecordSet.PictRecordSetApplication
20
- {
21
- constructor(pFable, pOptions, pServiceHash)
22
- {
23
- super(pFable, pOptions, pServiceHash);
24
-
25
- this.pict.addView('DoNothingView', {}, DoNothingView);
26
- this.pict.addProvider('FilterDataProvider', libPictSectionRecordSetFilterDataProvider);
27
- }
28
-
29
- /**
30
- * @param {function} fDone - Callback that finishes the test
31
- */
32
- set testDone(fDone)
33
- {
34
- this._testDone = fDone;
35
- }
36
-
37
- onAfterInitialize()
38
- {
39
- this.solve();
40
- this._testDone();
41
- return super.onAfterInitialize();
42
- }
43
- }
44
-
45
- class DoNothingView extends libPictView
46
- {
47
- constructor(pPict, pOptions)
48
- {
49
- super(pPict, pOptions);
50
- }
51
- }
52
-
53
- suite
54
- (
55
- 'PictSectionRecordSet Filter Data Provider Basic Tests',
56
- () =>
57
- {
58
- let originalLocalStorage;
59
- let _Pict = new libPict();
60
- _Pict.LogNoisiness = 1;
61
- //let _PictEnvironment = new libPict.EnvironmentObject(_Pict);
62
- localStorage = originalLocalStorage;
63
-
64
- setup(() =>
65
- {
66
- libBrowserEnv({
67
- url: "http://localhost/",
68
- });
69
- originalLocalStorage = localStorage;
70
- // @ts-ignore
71
- localStorage = {
72
- getItem: sinon.stub(),
73
- setItem: sinon.stub(),
74
- removeItem: sinon.stub(),
75
- };
76
- });
77
-
78
- teardown(() =>
79
- {
80
- sinon.restore();
81
- // @ts-ignore
82
- delete localStorage;
83
- localStorage = originalLocalStorage;
84
- });
85
-
86
- suite
87
- (
88
- 'Filter Data Provider Basic Tests',
89
- () =>
90
- {
91
- test(
92
- 'Basic Initialization',
93
- (fDone) =>
94
- {
95
- // Define view configuration
96
- let _Application = new DoNothingApplication(_Pict, {});
97
- let _DataFilterProvider = _Application.pict.providers.FilterDataProvider;
98
-
99
- _DataFilterProvider.storageProvider = localStorage
100
- let _StorageProvider = _DataFilterProvider.storageProvider;
101
-
102
- Expect(_DataFilterProvider).to.be.an('object', 'Filter Data Provider should be an object.');
103
- Expect(_StorageProvider).to.be.an.instanceof(originalLocalStorage.constructor, 'Storage Provider should be an instance of localStorage.');
104
-
105
- Expect(_Application).to.be.an('object', 'Application should be an object.');
106
- Expect(_Application).to.be.an.instanceof(libPictSectionRecordSet.PictRecordSetApplication, 'Application should be an instance of PictRecordSetApplication.');
107
-
108
- _Application.testDone = fDone;
109
-
110
- _Application.initialize();
111
-
112
- }
113
- );
114
- });
115
-
116
- suite
117
- (
118
- 'Filter Data Provider Apply Expected Filter Experience Tests',
119
- () =>
120
- {
121
- test(
122
- 'Apply Expected Filter Experience with no parameters',
123
- (fDone) =>
124
- {
125
- // Define view configuration
126
- let _Application = new DoNothingApplication(_Pict, {});
127
- let _DataFilterProvider = _Application.pict.providers.FilterDataProvider;
128
-
129
- _DataFilterProvider.storageProvider = localStorage
130
- let _StorageProvider = _DataFilterProvider.storageProvider;
131
-
132
- Expect(_DataFilterProvider).to.be.an('object', 'Filter Data Provider should be an object.');
133
- Expect(_StorageProvider).to.be.an.instanceof(originalLocalStorage.constructor, 'Storage Provider should be an instance of localStorage.');
134
-
135
- Expect(_Application).to.be.an('object', 'Application should be an object.');
136
- Expect(_Application).to.be.an.instanceof(libPictSectionRecordSet.PictRecordSetApplication, 'Application should be an instance of PictRecordSetApplication.');
137
-
138
- _Application.testDone = fDone;
139
-
140
- _Application.initialize();
141
-
142
- // Call applyExpectedFilterExperience with no parameters
143
- let result = _DataFilterProvider.applyExpectedFilterExperience();
144
-
145
- // TODO: Need better way to test this... Last used and default filter experiences are not accounted for in this test context.
146
- // For now, just check that the method runs and returns true (should be false if nothing is set in storage).
147
- Expect(result).to.be.true;
148
-
149
- }
150
- );
151
-
152
- test(
153
- 'Apply Expected Filter Experience with parameters',
154
- (fDone) =>
155
- {
156
- // Define view configuration
157
- let _Application = new DoNothingApplication(_Pict, {});
158
- let _DataFilterProvider = _Application.pict.providers.FilterDataProvider;
159
-
160
- _DataFilterProvider.storageProvider = localStorage
161
- let _StorageProvider = _DataFilterProvider.storageProvider;
162
-
163
- Expect(_DataFilterProvider).to.be.an('object', 'Filter Data Provider should be an object.');
164
- Expect(_StorageProvider).to.be.an.instanceof(originalLocalStorage.constructor, 'Storage Provider should be an instance of localStorage.');
165
-
166
- Expect(_Application).to.be.an('object', 'Application should be an object.');
167
- Expect(_Application).to.be.an.instanceof(libPictSectionRecordSet.PictRecordSetApplication, 'Application should be an instance of PictRecordSetApplication.');
168
-
169
- _Application.testDone = fDone;
170
-
171
- _Application.initialize();
172
-
173
- // Call applyExpectedFilterExperience with parameters
174
- let result = _DataFilterProvider.applyExpectedFilterExperience('TestRecordSet', 'TestViewContext');
175
-
176
- Expect(result).to.be.true;
177
-
178
- }
179
- );
180
-
181
- test(
182
- 'Modify localStorage to simulate last used filter experience and test Apply Expected Filter Experience',
183
- (fDone) =>
184
- {
185
- // Define view configuration
186
- let _Application = new DoNothingApplication(_Pict, {});
187
- let _DataFilterProvider = _Application.pict.providers.FilterDataProvider;
188
-
189
- _DataFilterProvider.storageProvider = localStorage
190
- let _StorageProvider = _DataFilterProvider.storageProvider;
191
-
192
- Expect(_DataFilterProvider).to.be.an('object', 'Filter Data Provider should be an object.');
193
- Expect(_StorageProvider).to.be.an.instanceof(originalLocalStorage.constructor, 'Storage Provider should be an instance of localStorage.');
194
-
195
- Expect(_Application).to.be.an('object', 'Application should be an object.');
196
- Expect(_Application).to.be.an.instanceof(libPictSectionRecordSet.PictRecordSetApplication, 'Application should be an instance of PictRecordSetApplication.');
197
-
198
- _Application.testDone = fDone;
199
-
200
- _Application.initialize();
201
-
202
- // Simulate last used filter experience in localStorage
203
- const testRecordSet = 'TestRecordSet';
204
- const testViewContext = 'TestViewContext';
205
- const testFilterExperienceHash = 'LastUsedHash123';
206
-
207
- const lastUsedKey = `Filter_MetaTest_${testRecordSet}_${testViewContext}_${test}`;
208
- localStorage.getItem.withArgs(lastUsedKey).returns(JSON.stringify({
209
- FilterExperienceHash: testFilterExperienceHash
210
- }));
211
-
212
- // Call applyExpectedFilterExperience with parameters
213
- let result = _DataFilterProvider.applyExpectedFilterExperience(testRecordSet, testViewContext, testFilterExperienceHash);
214
-
215
- Expect(result).to.be.true;
216
- Expect(localStorage.setItem.calledWith(lastUsedKey, sinon.match.string)).to.be.true;
217
-
218
- }
219
- );
220
-
221
- test(
222
- 'Modify localStorage to simuate creating settings for the default filter experience and test Apply Expected Filter Experience',
223
- (fDone) =>
224
- {
225
- // Define view configuration
226
- let _Application = new DoNothingApplication(_Pict, {});
227
- let _DataFilterProvider = _Application.pict.providers.FilterDataProvider;
228
-
229
- _DataFilterProvider.storageProvider = localStorage
230
- let _StorageProvider = _DataFilterProvider.storageProvider;
231
-
232
- Expect(_DataFilterProvider).to.be.an('object', 'Filter Data Provider should be an object.');
233
- Expect(_StorageProvider).to.be.an.instanceof(originalLocalStorage.constructor, 'Storage Provider should be an instance of localStorage.');
234
-
235
- Expect(_Application).to.be.an('object', 'Application should be an object.');
236
- Expect(_Application).to.be.an.instanceof(libPictSectionRecordSet.PictRecordSetApplication, 'Application should be an instance of PictRecordSetApplication.');
237
-
238
- _Application.testDone = fDone;
239
-
240
- _Application.initialize();
241
-
242
- // Simulate default filter experience settings in localStorage
243
- const testRecordSet = 'TestRecordSet';
244
- const testViewContext = 'TestViewContext';
245
- const testFilterExperienceHash = 'DefaultHash123';
246
-
247
- const defaultKey = `Filter_MetaTest_${testRecordSet}_${testViewContext}_SETTINGS`;
248
- localStorage.getItem.withArgs(defaultKey).returns(JSON.stringify({
249
- LastUsedFilterExperienceHash: testFilterExperienceHash
250
- }));
251
- // Call applyExpectedFilterExperience with parameters
252
- let result = _DataFilterProvider.applyExpectedFilterExperience(testRecordSet, testViewContext, testFilterExperienceHash);
253
-
254
- Expect(result).to.be.true;
255
- Expect(localStorage.setItem.calledWith(defaultKey, sinon.match.string)).to.be.true;
256
-
257
- }
258
- );
259
- }
260
-
261
- );
262
- }
263
- );
@@ -1,328 +0,0 @@
1
- /*
2
- Unit tests for Pict-Template-FilterInstanceViews.renderAsync.
3
-
4
- Exercises the parallel filter fan-out, per-filter transaction isolation,
5
- deferred post-render drain, render-epoch guard, and transaction-map cleanup
6
- added for the filter render performance refactor.
7
- */
8
-
9
- const libBrowserEnv = require('browser-env');
10
- libBrowserEnv({ url: 'http://localhost/' });
11
-
12
- const Chai = require('chai');
13
- const Expect = Chai.expect;
14
-
15
- const libPict = require('pict');
16
- const libPictView = require('pict-view');
17
- const libPictTemplateFilterInstanceViews = require('../source/templates/Pict-Template-FilterInstanceViews.js');
18
-
19
- /**
20
- * Minimal stub filter view that records every interaction and lets each test
21
- * control when the render and drain callbacks fire. The stub never touches
22
- * the DOM and never spawns sub-renders - everything is recorded so the tests
23
- * can inspect ordering.
24
- */
25
- class StubFilterView extends libPictView
26
- {
27
- constructor(pFable, pOptions, pServiceHash)
28
- {
29
- super(pFable, pOptions, pServiceHash);
30
- this.recordedCalls = [];
31
- this.pendingRenderCallbacks = [];
32
- this.pendingDrainCallbacks = [];
33
- this._fakeOutput = pOptions && pOptions.fakeOutput ? pOptions.fakeOutput : '<stub/>';
34
- }
35
-
36
- prepareRecord(pRecord)
37
- {
38
- this.recordedCalls.push({ type: 'prepareRecord', hash: pRecord && pRecord.Hash });
39
- }
40
-
41
- renderWithScopeAsync(pScope, pRenderableHash, pDestinationAddress, pRecord, pRootRenderable, fCallback)
42
- {
43
- this.recordedCalls.push({
44
- type: 'renderWithScopeAsync',
45
- rootRenderable: pRootRenderable,
46
- destinationAddress: pDestinationAddress,
47
- clauseHash: pRecord && pRecord.Hash,
48
- });
49
- // Simulate a real render writing into the pict template output cache.
50
- const tmpCacheKey = pDestinationAddress.split('.')[1];
51
- const tmpIndex = this.pendingRenderCallbacks.length;
52
- this.pict.__TemplateOutputCache[tmpCacheKey] = `${this._fakeOutput}-${tmpIndex}`;
53
- // Defer the callback until the test fires it manually.
54
- this.pendingRenderCallbacks.push(() => fCallback());
55
- }
56
-
57
- onAfterRenderAsync(fCallback, pRenderable)
58
- {
59
- this.recordedCalls.push({
60
- type: 'onAfterRenderAsync',
61
- rootRenderable: pRenderable,
62
- });
63
- // Defer the drain callback until the test fires it manually.
64
- this.pendingDrainCallbacks.push(() => fCallback());
65
- }
66
-
67
- fireAllRenderCallbacks()
68
- {
69
- while (this.pendingRenderCallbacks.length > 0)
70
- {
71
- this.pendingRenderCallbacks.shift()();
72
- }
73
- }
74
-
75
- fireAllDrainCallbacks()
76
- {
77
- while (this.pendingDrainCallbacks.length > 0)
78
- {
79
- this.pendingDrainCallbacks.shift()();
80
- }
81
- }
82
- }
83
-
84
- /**
85
- * Stand up a pict instance with just enough wiring for the FilterInstanceViews
86
- * template to run. Returns the pict instance, the template instance, and the
87
- * stub filter view.
88
- *
89
- * @param {Array<object>} pFilterClauses - Filter clauses to populate into Bundle._ActiveFilterState.
90
- */
91
- function buildHarness(pFilterClauses)
92
- {
93
- const _Pict = new libPict();
94
- _Pict.LogNoisiness = 0;
95
-
96
- // FilterInstanceViews.renderAsync expects pict.PictSectionRecordSet.recordSetProviderConfigurations
97
- // to resolve the record set. Stub the minimum shape.
98
- _Pict.PictSectionRecordSet = {
99
- recordSetProviderConfigurations: {
100
- TestRS: { RecordSet: 'TestRS' },
101
- },
102
- };
103
-
104
- // Register a stub filter view so _getViewForFilterClause returns it for
105
- // clauses of type 'StubFilterType'. Doing this BEFORE adding the
106
- // 'PRSP-Filters' fake entry below, because addView creates a real view
107
- // in the pict.views map and we need the hash to match what
108
- // _getViewForFilterClause looks up.
109
- _Pict.addView('PRSP-FilterType-StubFilterType', {}, StubFilterView);
110
-
111
- // FilterInstanceViews expects pict.views['PRSP-Filters'] to have a
112
- // _renderEpoch field so the deferred drain can snapshot + compare it.
113
- // Direct assignment on the views map is fine for a stub.
114
- _Pict.views['PRSP-Filters'] = { _renderEpoch: 0 };
115
-
116
- // Populate the active filter state that renderAsync reads.
117
- _Pict.Bundle._ActiveFilterState = {
118
- TestRS: { FilterClauses: pFilterClauses },
119
- };
120
-
121
- // Register the template instance we want to test and capture it.
122
- const tmpInstruction = _Pict.addTemplate(libPictTemplateFilterInstanceViews);
123
-
124
- return {
125
- pict: _Pict,
126
- instruction: tmpInstruction,
127
- stubView: _Pict.views['PRSP-FilterType-StubFilterType'],
128
- };
129
- }
130
-
131
- suite('PictSectionRecordSet FilterInstanceViews Render', () =>
132
- {
133
- suite('renderAsync', () =>
134
- {
135
- test('is a no-op when there are zero clauses', (fDone) =>
136
- {
137
- const tmpHarness = buildHarness([]);
138
- tmpHarness.instruction.renderAsync('', { RecordSet: 'TestRS' }, (pError, pResult) =>
139
- {
140
- Expect(pError).to.be.null;
141
- Expect(pResult).to.equal('');
142
- Expect(tmpHarness.stubView.recordedCalls).to.deep.equal([]);
143
- return fDone();
144
- });
145
- });
146
-
147
- test('fans out all filter renders in parallel before any callback fires', (fDone) =>
148
- {
149
- const tmpClauses = [
150
- { Type: 'StubFilterType', Hash: 'clauseA', FilterByColumn: 'A' },
151
- { Type: 'StubFilterType', Hash: 'clauseB', FilterByColumn: 'B' },
152
- { Type: 'StubFilterType', Hash: 'clauseC', FilterByColumn: 'C' },
153
- ];
154
- const tmpHarness = buildHarness(tmpClauses);
155
-
156
- let tmpFinalCallbackFired = false;
157
- tmpHarness.instruction.renderAsync('', { RecordSet: 'TestRS' }, () =>
158
- {
159
- tmpFinalCallbackFired = true;
160
- });
161
-
162
- // At this point every filter should have had renderWithScopeAsync
163
- // called, even though zero render callbacks have fired yet. A
164
- // sequential version would have only called renderWithScopeAsync
165
- // on the first filter at this point.
166
- const tmpRenderStarts = tmpHarness.stubView.recordedCalls.filter((c) => c.type === 'renderWithScopeAsync');
167
- Expect(tmpRenderStarts.length).to.equal(3, 'all three filter renders should have started in parallel');
168
- Expect(tmpFinalCallbackFired).to.equal(false, 'final callback must not fire until every render completes');
169
-
170
- // Fire all render callbacks, then finalize runs synchronously.
171
- tmpHarness.stubView.fireAllRenderCallbacks();
172
- Expect(tmpFinalCallbackFired).to.equal(true, 'final callback should fire once every render completes');
173
- return fDone();
174
- });
175
-
176
- test('concatenates per-filter output in clause order', (fDone) =>
177
- {
178
- const tmpClauses = [
179
- { Type: 'StubFilterType', Hash: 'clauseA', FilterByColumn: 'A' },
180
- { Type: 'StubFilterType', Hash: 'clauseB', FilterByColumn: 'B' },
181
- { Type: 'StubFilterType', Hash: 'clauseC', FilterByColumn: 'C' },
182
- ];
183
- const tmpHarness = buildHarness(tmpClauses);
184
-
185
- tmpHarness.instruction.renderAsync('', { RecordSet: 'TestRS' }, (pError, pResult) =>
186
- {
187
- Expect(pError).to.be.null;
188
- // Stub output is `<stub/>-N` where N is the render start index.
189
- // Since all three are started in order, the outputs are 0, 1, 2.
190
- Expect(pResult).to.equal('<stub/>-0<stub/>-1<stub/>-2');
191
- return fDone();
192
- });
193
- tmpHarness.stubView.fireAllRenderCallbacks();
194
- });
195
-
196
- test('each filter render gets its own transaction hash (not the dashboard root)', (fDone) =>
197
- {
198
- const tmpClauses = [
199
- { Type: 'StubFilterType', Hash: 'clauseA', FilterByColumn: 'A' },
200
- { Type: 'StubFilterType', Hash: 'clauseB', FilterByColumn: 'B' },
201
- ];
202
- const tmpHarness = buildHarness(tmpClauses);
203
-
204
- // Pass a fake dashboard root as pState.RootRenderable. The template
205
- // must NOT propagate this transaction hash into the per-filter
206
- // renders - each should have its own.
207
- const tmpFakeDashboardRoot =
208
- {
209
- TransactionHash: 'DashboardTx-shouldNotLeak',
210
- RootRenderableViewHash: 'PRSP-Dashboard-Stub',
211
- };
212
-
213
- tmpHarness.instruction.renderAsync('', { RecordSet: 'TestRS' }, () =>
214
- {
215
- const tmpRenderStarts = tmpHarness.stubView.recordedCalls.filter((c) => c.type === 'renderWithScopeAsync');
216
- const tmpTransactionHashes = tmpRenderStarts.map((c) => c.rootRenderable.TransactionHash);
217
-
218
- Expect(tmpTransactionHashes).to.have.lengthOf(2);
219
- Expect(tmpTransactionHashes[0]).to.not.equal('DashboardTx-shouldNotLeak');
220
- Expect(tmpTransactionHashes[1]).to.not.equal('DashboardTx-shouldNotLeak');
221
- Expect(tmpTransactionHashes[0]).to.not.equal(tmpTransactionHashes[1]);
222
- Expect(tmpTransactionHashes[0]).to.match(/^FilterInstance-/);
223
- Expect(tmpTransactionHashes[1]).to.match(/^FilterInstance-/);
224
- return fDone();
225
- }, null, null, { RootRenderable: tmpFakeDashboardRoot });
226
-
227
- tmpHarness.stubView.fireAllRenderCallbacks();
228
- });
229
-
230
- test('fires fCallback BEFORE draining deferred post-render work', (fDone) =>
231
- {
232
- const tmpClauses = [
233
- { Type: 'StubFilterType', Hash: 'clauseA', FilterByColumn: 'A' },
234
- { Type: 'StubFilterType', Hash: 'clauseB', FilterByColumn: 'B' },
235
- ];
236
- const tmpHarness = buildHarness(tmpClauses);
237
-
238
- let tmpFinalCallbackFired = false;
239
- tmpHarness.instruction.renderAsync('', { RecordSet: 'TestRS' }, () =>
240
- {
241
- tmpFinalCallbackFired = true;
242
- });
243
-
244
- // Complete all filter renders synchronously.
245
- tmpHarness.stubView.fireAllRenderCallbacks();
246
- Expect(tmpFinalCallbackFired).to.equal(true, 'fCallback should have fired once every render completed');
247
-
248
- // At this exact moment no drain has run yet: the drain is queued
249
- // behind a setTimeout(0) macrotask.
250
- const tmpDrainsBeforeTick = tmpHarness.stubView.recordedCalls.filter((c) => c.type === 'onAfterRenderAsync').length;
251
- Expect(tmpDrainsBeforeTick).to.equal(0, 'no drain should have run yet - it is scheduled on the next macrotask');
252
-
253
- // Let the setTimeout(0) fire.
254
- setTimeout(() =>
255
- {
256
- const tmpDrainsAfterTick = tmpHarness.stubView.recordedCalls.filter((c) => c.type === 'onAfterRenderAsync').length;
257
- Expect(tmpDrainsAfterTick).to.equal(2, 'both filter drains should have run on the next macrotask');
258
- return fDone();
259
- }, 5);
260
- });
261
-
262
- test('epoch guard: a drain scheduled before a filter re-render is skipped', (fDone) =>
263
- {
264
- const tmpClauses = [
265
- { Type: 'StubFilterType', Hash: 'clauseA', FilterByColumn: 'A' },
266
- ];
267
- const tmpHarness = buildHarness(tmpClauses);
268
- const tmpFiltersView = tmpHarness.pict.views['PRSP-Filters'];
269
-
270
- tmpHarness.instruction.renderAsync('', { RecordSet: 'TestRS' }, () => {});
271
- tmpHarness.stubView.fireAllRenderCallbacks();
272
-
273
- // Simulate a filter mutation that bumps the epoch between the
274
- // dashboard callback firing and the setTimeout drain running.
275
- tmpFiltersView._renderEpoch = tmpFiltersView._renderEpoch + 1;
276
-
277
- setTimeout(() =>
278
- {
279
- const tmpDrainCalls = tmpHarness.stubView.recordedCalls.filter((c) => c.type === 'onAfterRenderAsync').length;
280
- Expect(tmpDrainCalls).to.equal(0, 'drain should have been skipped because the epoch changed');
281
- return fDone();
282
- }, 5);
283
- });
284
-
285
- test('transaction map entries for filter renders are cleaned up after the drain', (fDone) =>
286
- {
287
- const tmpClauses = [
288
- { Type: 'StubFilterType', Hash: 'clauseA', FilterByColumn: 'A' },
289
- { Type: 'StubFilterType', Hash: 'clauseB', FilterByColumn: 'B' },
290
- ];
291
- const tmpHarness = buildHarness(tmpClauses);
292
-
293
- tmpHarness.instruction.renderAsync('', { RecordSet: 'TestRS' }, () => {});
294
- tmpHarness.stubView.fireAllRenderCallbacks();
295
-
296
- // Right after fCallback fires, both filter transactions are
297
- // registered in the map (the drain has not run yet).
298
- const tmpFilterKeysBefore = Object.keys(tmpHarness.pict.TransactionTracking.transactionMap)
299
- .filter((k) => k.startsWith('FilterInstance-'));
300
- Expect(tmpFilterKeysBefore.length).to.equal(2, 'both filter transactions should be registered before the drain runs');
301
-
302
- setTimeout(() =>
303
- {
304
- // The drain kicks off both filters' onAfterRenderAsync calls.
305
- // Fire their drain completion callbacks so the cleanup runs.
306
- tmpHarness.stubView.fireAllDrainCallbacks();
307
-
308
- const tmpFilterKeysAfter = Object.keys(tmpHarness.pict.TransactionTracking.transactionMap)
309
- .filter((k) => k.startsWith('FilterInstance-'));
310
- Expect(tmpFilterKeysAfter.length).to.equal(0, 'transaction map should be cleaned up after the drain completes');
311
- return fDone();
312
- }, 5);
313
- });
314
-
315
- test('bails early with empty result when the record set is not configured', (fDone) =>
316
- {
317
- const tmpHarness = buildHarness([
318
- { Type: 'StubFilterType', Hash: 'clauseA', FilterByColumn: 'A' },
319
- ]);
320
- tmpHarness.instruction.renderAsync('', { RecordSet: 'NotConfigured' }, (pError, pResult) =>
321
- {
322
- Expect(pError).to.be.null;
323
- Expect(pResult).to.equal('');
324
- return fDone();
325
- });
326
- });
327
- });
328
- });