@@ -187,3 +187,153 @@ 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+ expect . assertions ( 2 ) ;
200+ store . addCellListener ( 't1' , 'r1' , 'c1' , ( _store , _table , _row , _cell , newCell , oldCell ) => {
201+ expect ( newCell ) . toBe ( null ) ;
202+ expect ( oldCell ) . toBeUndefined ( ) ;
203+ } ) ;
204+
205+ store . setCell ( 't1' , 'r1' , 'c1' , null ) ;
206+ } ) ;
207+
208+ test ( 'Cell listener fires when changing from null' , ( ) => {
209+ store . setCell ( 't1' , 'r1' , 'c1' , null ) ;
210+ expect . assertions ( 2 ) ;
211+ store . addCellListener ( 't1' , 'r1' , 'c1' , ( _store , _table , _row , _cell , newCell , oldCell ) => {
212+ expect ( newCell ) . toBe ( 'hello' ) ;
213+ expect ( oldCell ) . toBe ( null ) ;
214+ } ) ;
215+
216+ store . setCell ( 't1' , 'r1' , 'c1' , 'hello' ) ;
217+ } ) ;
218+
219+ test ( 'Value listener fires when setting to null' , ( ) => {
220+ expect . assertions ( 2 ) ;
221+ store . addValueListener ( 'v1' , ( _store , _valueId , newValue , oldValue ) => {
222+ expect ( newValue ) . toBe ( null ) ;
223+ expect ( oldValue ) . toBeUndefined ( ) ;
224+ } ) ;
225+
226+ store . setValue ( 'v1' , null ) ;
227+ } ) ;
228+
229+ test ( 'HasCell listener distinguishes null from deleted' , ( ) => {
230+ expect . assertions ( 2 ) ;
231+ let callCount = 0 ;
232+ store . addHasCellListener ( 't1' , 'r1' , 'c1' , ( _store , _table , _row , _cell , hasCell ) => {
233+ if ( callCount === 0 ) {
234+ expect ( hasCell ) . toBe ( true ) ;
235+ } else if ( callCount === 1 ) {
236+ expect ( hasCell ) . toBe ( false ) ;
237+ }
238+ callCount ++ ;
239+ } ) ;
240+
241+ store . setCell ( 't1' , 'r1' , 'c1' , null ) ;
242+ store . delCell ( 't1' , 'r1' , 'c1' ) ;
243+ } ) ;
244+ } ) ;
245+
246+ describe ( 'Null vs Delete semantics' , ( ) => {
247+ let store : Store ;
248+
249+ beforeEach ( ( ) => {
250+ store = createStore ( ) ;
251+ } ) ;
252+
253+ test ( 'setCell(null) creates a cell, delCell removes it' , ( ) => {
254+ store . setCell ( 't1' , 'r1' , 'c1' , null ) ;
255+ expect ( store . hasCell ( 't1' , 'r1' , 'c1' ) ) . toBe ( true ) ;
256+ expect ( store . getCell ( 't1' , 'r1' , 'c1' ) ) . toBe ( null ) ;
257+ expect ( store . getCellIds ( 't1' , 'r1' ) ) . toEqual ( [ 'c1' ] ) ;
258+
259+ store . delCell ( 't1' , 'r1' , 'c1' ) ;
260+ expect ( store . hasCell ( 't1' , 'r1' , 'c1' ) ) . toBe ( false ) ;
261+ expect ( store . getCell ( 't1' , 'r1' , 'c1' ) ) . toBeUndefined ( ) ;
262+ expect ( store . getCellIds ( 't1' , 'r1' ) ) . toEqual ( [ ] ) ;
263+ } ) ;
264+
265+ test ( 'setValue(null) creates a value, delValue removes it' , ( ) => {
266+ store . setValue ( 'v1' , null ) ;
267+ expect ( store . hasValue ( 'v1' ) ) . toBe ( true ) ;
268+ expect ( store . getValue ( 'v1' ) ) . toBe ( null ) ;
269+ expect ( store . getValueIds ( ) ) . toEqual ( [ 'v1' ] ) ;
270+
271+ store . delValue ( 'v1' ) ;
272+ expect ( store . hasValue ( 'v1' ) ) . toBe ( false ) ;
273+ expect ( store . getValue ( 'v1' ) ) . toBeUndefined ( ) ;
274+ expect ( store . getValueIds ( ) ) . toEqual ( [ ] ) ;
275+ } ) ;
276+
277+ test ( 'Null cells appear in iteration' , ( ) => {
278+ store . setTables ( {
279+ t1 : {
280+ r1 : { c1 : 'hello' , c2 : null , c3 : 42 } ,
281+ } ,
282+ } ) ;
283+
284+ const cells : [ string , any ] [ ] = [ ] ;
285+ store . forEachCell ( 't1' , 'r1' , ( cellId , cell ) => {
286+ cells . push ( [ cellId , cell ] ) ;
287+ } ) ;
288+
289+ expect ( cells ) . toEqual ( [
290+ [ 'c1' , 'hello' ] ,
291+ [ 'c2' , null ] ,
292+ [ 'c3' , 42 ] ,
293+ ] ) ;
294+ } ) ;
295+
296+ test ( 'Null values appear in iteration' , ( ) => {
297+ store . setValues ( { v1 : 'test' , v2 : null , v3 : 123 } ) ;
298+
299+ const values : [ string , any ] [ ] = [ ] ;
300+ store . forEachValue ( ( valueId , value ) => {
301+ values . push ( [ valueId , value ] ) ;
302+ } ) ;
303+
304+ expect ( values ) . toEqual ( [
305+ [ 'v1' , 'test' ] ,
306+ [ 'v2' , null ] ,
307+ [ 'v3' , 123 ] ,
308+ ] ) ;
309+ } ) ;
310+ } ) ;
311+
312+ describe ( 'Transactions with null' , ( ) => {
313+ let store : Store ;
314+
315+ beforeEach ( ( ) => {
316+ store = createStore ( ) ;
317+ } ) ;
318+
319+ test ( 'Transaction can handle null values' , ( ) => {
320+ store . setCell ( 't1' , 'r1' , 'c1' , 'initial' ) ;
321+
322+ store . startTransaction ( ) ;
323+ store . setCell ( 't1' , 'r1' , 'c1' , null ) ;
324+ expect ( store . getCell ( 't1' , 'r1' , 'c1' ) ) . toBe ( null ) ;
325+ store . finishTransaction ( ) ;
326+
327+ expect ( store . getCell ( 't1' , 'r1' , 'c1' ) ) . toBe ( null ) ;
328+ } ) ;
329+
330+ test ( 'Transaction can commit null values' , ( ) => {
331+ store . transaction ( ( ) => {
332+ store . setCell ( 't1' , 'r1' , 'c1' , null ) ;
333+ store . setValue ( 'v1' , null ) ;
334+ } ) ;
335+
336+ expect ( store . getCell ( 't1' , 'r1' , 'c1' ) ) . toBe ( null ) ;
337+ expect ( store . getValue ( 'v1' ) ) . toBe ( null ) ;
338+ } ) ;
339+ } ) ;
0 commit comments