/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { describe, it, beforeEach } from 'node:test'; import assert from 'node:assert'; import { createVisibilitySlice, type VisibilitySlice } from './visibilitySlice.js'; import { getPersistedTypeVisibility } from '../constants.js'; describe('VisibilitySlice', () => { let state: VisibilitySlice; let setState: (partial: Partial | ((state: VisibilitySlice) => Partial)) => void; beforeEach(() => { setState = (partial) => { if (typeof partial === 'function') { const updates = partial(state); state = { ...state, ...updates }; } else { state = { ...state, ...partial }; } }; state = createVisibilitySlice(setState, () => state, {} as any); }); describe('initial state', () => { it('should initialise type visibility from persisted preferences', () => { const persisted = getPersistedTypeVisibility(); assert.strictEqual(state.typeVisibility.spaces, persisted.spaces); assert.strictEqual(state.typeVisibility.openings, persisted.openings); assert.strictEqual(state.typeVisibility.site, persisted.site); assert.strictEqual(state.typeVisibility.ifcAnnotations, persisted.ifcAnnotations); assert.strictEqual(state.typeVisibility.ifcGrid, persisted.ifcGrid); }); }); describe('multi-model visibility: hideEntityInModel', () => { it('should create new set for model if not exists', () => { state.hideEntityInModel('model-1', 100); state.hideEntityInModel('model-1', 200); const hidden = state.hiddenEntitiesByModel.get('model-1'); assert.strictEqual(hidden?.size, 2); }); it('should keep models separate', () => { state.hideEntityInModel('model-1', 100); state.hideEntityInModel('model-2', 200); assert.strictEqual(state.hiddenEntitiesByModel.get('model-1')?.size, 1); assert.strictEqual(state.hiddenEntitiesByModel.get('model-2')?.size, 1); assert.ok(state.hiddenEntitiesByModel.get('model-1')?.has(100)); assert.ok(state.hiddenEntitiesByModel.get('model-2')?.has(200)); }); }); describe('multi-model visibility: hideEntitiesInModel', () => { it('should hide multiple entities', () => { state.hideEntitiesInModel('model-1', [100, 200, 300]); const hidden = state.hiddenEntitiesByModel.get('model-1'); assert.strictEqual(hidden?.size, 3); assert.ok(hidden?.has(100)); assert.ok(hidden?.has(200)); assert.ok(hidden?.has(300)); }); }); describe('multi-model visibility: showEntityInModel', () => { it('should show hidden entity', () => { state.hideEntityInModel('model-1', 123); state.showEntityInModel('model-1', 123); const hidden = state.hiddenEntitiesByModel.get('model-1'); // Set should be removed when empty assert.strictEqual(hidden, undefined); }); it('should do nothing for non-hidden entity', () => { state.showEntityInModel('model-1', 123); // Should not throw, just do nothing assert.strictEqual(state.hiddenEntitiesByModel.size, 0); }); it('should remove model from map when all entities shown', () => { state.hideEntityInModel('model-1', 100); state.hideEntityInModel('model-1', 200); state.showEntityInModel('model-1', 100); state.showEntityInModel('model-1', 200); assert.ok(!state.hiddenEntitiesByModel.has('model-1')); }); }); describe('multi-model visibility: showEntitiesInModel', () => { it('should show multiple entities', () => { state.hideEntitiesInModel('model-1', [100, 200, 300]); state.showEntitiesInModel('model-1', [100, 200]); const hidden = state.hiddenEntitiesByModel.get('model-1'); assert.strictEqual(hidden?.size, 1); assert.ok(hidden?.has(300)); }); }); describe('multi-model visibility: toggleEntityVisibilityInModel', () => { it('should hide visible entity', () => { state.toggleEntityVisibilityInModel('model-1', 123); const hidden = state.hiddenEntitiesByModel.get('model-1'); assert.ok(hidden?.has(123)); }); it('should show hidden entity', () => { state.hideEntityInModel('model-1', 123); state.toggleEntityVisibilityInModel('model-1', 123); // Set should be removed when empty assert.ok(!state.hiddenEntitiesByModel.has('model-1')); }); }); describe('multi-model visibility: isEntityVisibleInModel', () => { it('should return true for visible entity', () => { assert.strictEqual(state.isEntityVisibleInModel('model-1', 123), true); }); it('should return false for hidden entity', () => { state.hideEntityInModel('model-1', 123); assert.strictEqual(state.isEntityVisibleInModel('model-1', 123), false); }); it('should distinguish between models', () => { state.hideEntityInModel('model-1', 123); assert.strictEqual(state.isEntityVisibleInModel('model-1', 123), false); assert.strictEqual(state.isEntityVisibleInModel('model-2', 123), true); }); }); describe('multi-model visibility: getHiddenEntitiesForModel', () => { it('should return hidden entities for model', () => { state.hideEntitiesInModel('model-1', [100, 200, 300]); const hidden = state.getHiddenEntitiesForModel('model-1'); assert.strictEqual(hidden.size, 3); assert.ok(hidden.has(100)); assert.ok(hidden.has(200)); assert.ok(hidden.has(300)); }); it('should return empty set for model with no hidden entities', () => { const hidden = state.getHiddenEntitiesForModel('non-existent'); assert.strictEqual(hidden.size, 0); }); }); describe('multi-model visibility: clearModelVisibility', () => { it('should clear visibility state for model', () => { state.hideEntitiesInModel('model-1', [100, 200]); state.clearModelVisibility('model-1'); assert.ok(!state.hiddenEntitiesByModel.has('model-1')); assert.ok(!state.isolatedEntitiesByModel.has('model-1')); }); it('should not affect other models', () => { state.hideEntitiesInModel('model-1', [100]); state.hideEntitiesInModel('model-2', [200]); state.clearModelVisibility('model-1'); assert.ok(!state.hiddenEntitiesByModel.has('model-1')); assert.ok(state.hiddenEntitiesByModel.has('model-2')); }); }); describe('multi-model visibility: showAllInAllModels', () => { it('should clear all visibility state', () => { // Set up some state state.hideEntitiesInModel('model-1', [100, 200]); state.hideEntitiesInModel('model-2', [300, 400]); state.hideEntity(500); // Legacy state.showAllInAllModels(); assert.strictEqual(state.hiddenEntitiesByModel.size, 0); assert.strictEqual(state.isolatedEntitiesByModel.size, 0); assert.strictEqual(state.hiddenEntities.size, 0); assert.strictEqual(state.isolatedEntities, null); }); }); describe('legacy visibility: showEntity', () => { it('should show hidden entity', () => { state.hideEntity(123); state.showEntity(123); assert.ok(!state.hiddenEntities.has(123)); }); }); describe('legacy visibility: toggleEntityVisibility', () => { it('should toggle visibility', () => { state.toggleEntityVisibility(123); assert.ok(state.hiddenEntities.has(123)); state.toggleEntityVisibility(123); assert.ok(!state.hiddenEntities.has(123)); }); }); describe('legacy visibility: isolateEntity', () => { it('should isolate single entity', () => { state.isolateEntity(123); assert.ok(state.isolatedEntities?.has(123)); assert.strictEqual(state.isolatedEntities?.size, 1); }); it('should toggle isolation off when re-isolating same entity', () => { state.isolateEntity(123); state.isolateEntity(123); assert.strictEqual(state.isolatedEntities, null); }); }); describe('legacy visibility: clearIsolation', () => { it('should clear isolation', () => { state.isolateEntity(123); state.clearIsolation(); assert.strictEqual(state.isolatedEntities, null); }); }); describe('legacy visibility: showAll', () => { it('should clear all visibility state', () => { state.hideEntity(123); state.isolateEntity(456); state.showAll(); assert.strictEqual(state.hiddenEntities.size, 0); assert.strictEqual(state.isolatedEntities, null); }); }); describe('legacy visibility: isEntityVisible', () => { it('should return true for visible entity', () => { assert.strictEqual(state.isEntityVisible(123), true); }); it('should return false for hidden entity', () => { state.hideEntity(123); assert.strictEqual(state.isEntityVisible(123), false); }); it('should return false for non-isolated entity when isolation active', () => { state.isolateEntity(100); assert.strictEqual(state.isEntityVisible(100), true); assert.strictEqual(state.isEntityVisible(200), false); }); }); describe('type visibility: toggleTypeVisibility', () => { it('should toggle each type key independently', () => { const keys = ['spaces', 'openings', 'site', 'ifcAnnotations', 'ifcGrid'] as const; for (const key of keys) { const before = { ...state.typeVisibility }; state.toggleTypeVisibility(key); assert.strictEqual(state.typeVisibility[key], !before[key], `toggle ${key}`); for (const other of keys) { if (other === key) continue; assert.strictEqual( state.typeVisibility[other], before[other], `toggling ${key} must not change ${other}`, ); } } }); it('resetTypeVisibility restores semantic defaults', () => { // Flip everything away from defaults first. state.toggleTypeVisibility('spaces'); // false -> true state.toggleTypeVisibility('site'); // true -> false state.toggleTypeVisibility('ifcGrid'); // true -> false state.resetTypeVisibility(); assert.deepStrictEqual(state.typeVisibility, { spaces: false, spatialZones: false, openings: false, virtualElements: false, site: true, ifcAnnotations: true, ifcGrid: true, }); }); }); });