import { describe, expect, test } from 'vitest'; import { isNameTaken, makeUniqueName } from './naming'; describe('isNameTaken', () => { const datasets = [ { id: 1, name: 'Sales' }, { id: 2, name: 'Regions' }, ]; test('matches case-insensitively', () => { expect(isNameTaken('sales', datasets)).toBe(true); expect(isNameTaken('SALES', datasets)).toBe(true); expect(isNameTaken('Unknown', datasets)).toBe(false); }); test('trims the desired name before comparing', () => { expect(isNameTaken(' Sales ', datasets)).toBe(true); }); test('excludeId lets a record ignore itself (e.g. a case-only rename)', () => { expect(isNameTaken('Sales', datasets, 1)).toBe(false); expect(isNameTaken('sales', datasets, 1)).toBe(false); // Renaming to a name owned by a *different* record is still taken. expect(isNameTaken('Regions', datasets, 1)).toBe(true); }); }); describe('makeUniqueName', () => { test('returns the trimmed desired name when free', () => { expect(makeUniqueName('Fresh', ['Sales'])).toBe('Fresh'); expect(makeUniqueName(' Fresh ', ['Sales'])).toBe('Fresh'); }); test('suffixes collisions: Name -> Name 2 -> Name 3', () => { expect(makeUniqueName('Name', ['Name'])).toBe('Name 2'); expect(makeUniqueName('Name', ['Name', 'Name 2'])).toBe('Name 3'); }); test('comparison is case-insensitive but casing is preserved', () => { expect(makeUniqueName('Name', ['name'])).toBe('Name 2'); expect(makeUniqueName('MyData', ['mydata'])).toBe('MyData 2'); }); test('within-batch reservation is the caller responsibility: a single call does not reserve', () => { // Two consecutive calls with the same existing set both yield "Name 2" — // the caller must reserve each chosen name as it goes (arch 07 §5). const existing = ['Name']; expect(makeUniqueName('Name', existing)).toBe('Name 2'); expect(makeUniqueName('Name', existing)).toBe('Name 2'); // Reserving manually advances to the next free slot. expect(makeUniqueName('Name', [...existing, 'Name 2'])).toBe('Name 3'); }); });