Skip to content

Commit 47647ea

Browse files
committed
[null] Phase 2 & 3: Add comprehensive tests for listeners, delete semantics, and transactions
1 parent 3a98bb4 commit 47647ea

1 file changed

Lines changed: 165 additions & 0 deletions

File tree

test/unit/core/store/store-nulls.test.ts

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,3 +187,168 @@ describe('Null with schemas', () => {
187187
expect(store.getCell('t1', 'r1', 'name')).toBe('Alice');
188188
});
189189
});
190+
191+
describe('Null listeners and events', () => {
192+
let store: Store;
193+
194+
beforeEach(() => {
195+
store = createStore();
196+
});
197+
198+
test('Cell listener fires when setting to null', () => {
199+
let called = 0;
200+
let newCell;
201+
let oldCell;
202+
store.addCellListener('t1', 'r1', 'c1', (_store, _t, _r, _c, nCell, oCell) => {
203+
called++;
204+
newCell = nCell;
205+
oldCell = oCell;
206+
});
207+
208+
store.setCell('t1', 'r1', 'c1', null);
209+
expect(called).toBe(1);
210+
expect(newCell).toBe(null);
211+
expect(oldCell).toBeUndefined();
212+
});
213+
214+
test('Cell listener fires when changing from null', () => {
215+
store.setCell('t1', 'r1', 'c1', null);
216+
let called = 0;
217+
let newCell;
218+
let oldCell;
219+
store.addCellListener('t1', 'r1', 'c1', (_store, _t, _r, _c, nCell, oCell) => {
220+
called++;
221+
newCell = nCell;
222+
oldCell = oCell;
223+
});
224+
225+
store.setCell('t1', 'r1', 'c1', 'hello');
226+
expect(called).toBe(1);
227+
expect(newCell).toBe('hello');
228+
expect(oldCell).toBe(null);
229+
});
230+
231+
test('Value listener fires when setting to null', () => {
232+
let called = 0;
233+
let newValue;
234+
let oldValue;
235+
store.addValueListener('v1', (_store, _v, nValue, oValue) => {
236+
called++;
237+
newValue = nValue;
238+
oldValue = oValue;
239+
});
240+
241+
store.setValue('v1', null);
242+
expect(called).toBe(1);
243+
expect(newValue).toBe(null);
244+
expect(oldValue).toBeUndefined();
245+
});
246+
247+
test('HasCell listener distinguishes null from deleted', () => {
248+
let calls: boolean[] = [];
249+
store.addHasCellListener('t1', 'r1', 'c1', (_store, _t, _r, _c, hasCell) => {
250+
calls.push(hasCell);
251+
});
252+
253+
store.setCell('t1', 'r1', 'c1', null);
254+
expect(calls).toEqual([true]);
255+
256+
store.delCell('t1', 'r1', 'c1');
257+
expect(calls).toEqual([true, false]);
258+
});
259+
});
260+
261+
describe('Null vs Delete semantics', () => {
262+
let store: Store;
263+
264+
beforeEach(() => {
265+
store = createStore();
266+
});
267+
268+
test('setCell(null) creates a cell, delCell removes it', () => {
269+
store.setCell('t1', 'r1', 'c1', null);
270+
expect(store.hasCell('t1', 'r1', 'c1')).toBe(true);
271+
expect(store.getCell('t1', 'r1', 'c1')).toBe(null);
272+
expect(store.getCellIds('t1', 'r1')).toEqual(['c1']);
273+
274+
store.delCell('t1', 'r1', 'c1');
275+
expect(store.hasCell('t1', 'r1', 'c1')).toBe(false);
276+
expect(store.getCell('t1', 'r1', 'c1')).toBeUndefined();
277+
expect(store.getCellIds('t1', 'r1')).toEqual([]);
278+
});
279+
280+
test('setValue(null) creates a value, delValue removes it', () => {
281+
store.setValue('v1', null);
282+
expect(store.hasValue('v1')).toBe(true);
283+
expect(store.getValue('v1')).toBe(null);
284+
expect(store.getValueIds()).toEqual(['v1']);
285+
286+
store.delValue('v1');
287+
expect(store.hasValue('v1')).toBe(false);
288+
expect(store.getValue('v1')).toBeUndefined();
289+
expect(store.getValueIds()).toEqual([]);
290+
});
291+
292+
test('Null cells appear in iteration', () => {
293+
store.setTables({
294+
t1: {
295+
r1: {c1: 'hello', c2: null, c3: 42},
296+
},
297+
});
298+
299+
const cells: [string, any][] = [];
300+
store.forEachCell('t1', 'r1', (cellId, cell) => {
301+
cells.push([cellId, cell]);
302+
});
303+
304+
expect(cells).toEqual([
305+
['c1', 'hello'],
306+
['c2', null],
307+
['c3', 42],
308+
]);
309+
});
310+
311+
test('Null values appear in iteration', () => {
312+
store.setValues({v1: 'test', v2: null, v3: 123});
313+
314+
const values: [string, any][] = [];
315+
store.forEachValue((valueId, value) => {
316+
values.push([valueId, value]);
317+
});
318+
319+
expect(values).toEqual([
320+
['v1', 'test'],
321+
['v2', null],
322+
['v3', 123],
323+
]);
324+
});
325+
});
326+
327+
describe('Transactions with null', () => {
328+
let store: Store;
329+
330+
beforeEach(() => {
331+
store = createStore();
332+
});
333+
334+
test('Transaction can handle null values', () => {
335+
store.setCell('t1', 'r1', 'c1', 'initial');
336+
337+
store.startTransaction();
338+
store.setCell('t1', 'r1', 'c1', null);
339+
expect(store.getCell('t1', 'r1', 'c1')).toBe(null);
340+
store.finishTransaction();
341+
342+
expect(store.getCell('t1', 'r1', 'c1')).toBe(null);
343+
});
344+
345+
test('Transaction can commit null values', () => {
346+
store.transaction(() => {
347+
store.setCell('t1', 'r1', 'c1', null);
348+
store.setValue('v1', null);
349+
});
350+
351+
expect(store.getCell('t1', 'r1', 'c1')).toBe(null);
352+
expect(store.getValue('v1')).toBe(null);
353+
});
354+
});

0 commit comments

Comments
 (0)