@@ -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