import { describe, expect, it } from 'vitest';

import { CURRENT_THEME_VERSION } from './custom-theme';
import { CURRENT_DATASET_VERSION, type Dataset } from './dataset';
import { createFontAsset, serializeFontAsset, type FontAsset, type FontFormat } from './font-asset';
import {
  applyDatasetRenamesToSnippets,
  dedupeIncomingNames,
  dropClashingFonts,
  normalizeImport,
  reassignCollidingSnippetIds,
} from './import-normalize';
import { CURRENT_SNIPPET_VERSION, type Snippet } from './snippet';

/** Deterministic id generator returning id-1, id-2, … */
function counterIds(prefix = 'id'): () => string {
  let n = 0;
  return () => `${prefix}-${++n}`;
}

const FIXED_NOW = new Date('2026-06-07T10:00:00.000Z');
const FIXED_NOW_ISO = FIXED_NOW.toISOString();

/** Parse a spec string and read its top-level `data.name` (typed for lint). */
function dataName(spec: string): string {
  return (JSON.parse(spec) as { data: { name: string } }).data.name;
}

/** A fully-current snippet record (carries an ISO `created`). */
function currentSnippetRecord(over: Partial<Snippet> = {}): Record<string, unknown> {
  return {
    id: 's-existing',
    version: CURRENT_SNIPPET_VERSION,
    name: 'Bar chart',
    created: '2025-01-02T03:04:05.000Z',
    modified: '2025-02-02T03:04:05.000Z',
    spec: '{"mark":"bar"}',
    draftSpec: '{"mark":"bar"}',
    comment: 'hi',
    tags: ['fav'],
    datasetRefs: ['Sales'],
    meta: { k: 1 },
    ...over,
  };
}

/** A minimal current dataset record. */
function datasetRecord(over: Partial<Dataset> = {}): Dataset {
  return {
    id: 1,
    version: CURRENT_DATASET_VERSION,
    name: 'Sales',
    data: 'a,b\n1,2',
    format: 'csv',
    source: 'inline',
    comment: '',
    rowCount: 1,
    columnCount: 2,
    columns: ['a', 'b'],
    columnTypes: [
      { name: 'a', type: 'number' },
      { name: 'b', type: 'number' },
    ],
    columnStats: [
      { name: 'a', distinct: 1, distinctCapped: false, numericExtent: { min: 1, max: 1 } },
      { name: 'b', distinct: 1, distinctCapped: false, numericExtent: { min: 2, max: 2 } },
    ],
    size: 7,
    created: '2025-01-01T00:00:00.000Z',
    modified: '2025-01-01T00:00:00.000Z',
    ...over,
  };
}

describe('normalizeImport — shape detection', () => {
  it('recognizes the Astrolabe export envelope (snippets + datasets)', () => {
    const parsed = {
      version: '1.0',
      exportedBy: 'Astrolabe',
      snippets: [currentSnippetRecord()],
      datasets: [datasetRecord()],
    };
    const result = normalizeImport(parsed, { now: FIXED_NOW });
    expect(result.snippets).toHaveLength(1);
    expect(result.datasets).toHaveLength(1);
    expect(result.datasets[0].name).toBe('Sales');
  });

  it('treats a top-level array as a bare list of snippets (no datasets)', () => {
    const parsed = [currentSnippetRecord({ id: 'a' }), currentSnippetRecord({ id: 'b' })];
    const result = normalizeImport(parsed, { now: FIXED_NOW });
    expect(result.snippets).toHaveLength(2);
    expect(result.datasets).toEqual([]);
  });

  it('treats a non-envelope object as a single snippet', () => {
    const parsed = currentSnippetRecord();
    const result = normalizeImport(parsed, { now: FIXED_NOW });
    expect(result.snippets).toHaveLength(1);
    expect(result.datasets).toEqual([]);
  });

  it('treats an object with version but no snippets array as a single snippet', () => {
    // `version` is a record field here, not an envelope marker — no snippets array.
    const parsed = currentSnippetRecord();
    const result = normalizeImport(parsed, { now: FIXED_NOW });
    expect(result.snippets).toHaveLength(1);
    expect(result.snippets[0].name).toBe('Bar chart');
  });

  it('returns empty for null / non-object / non-array junk', () => {
    const empty = { snippets: [], datasets: [], themes: [], fonts: [] };
    expect(normalizeImport(null)).toEqual(empty);
    expect(normalizeImport(42)).toEqual(empty);
    expect(normalizeImport('hello')).toEqual(empty);
    expect(normalizeImport(undefined)).toEqual(empty);
  });

  it('ignores a non-array datasets field on the envelope', () => {
    const parsed = { version: '1.0', snippets: [currentSnippetRecord()], datasets: 'nope' };
    const result = normalizeImport(parsed, { now: FIXED_NOW });
    expect(result.datasets).toEqual([]);
  });

  it('reads an optional themes array on the envelope (absent → empty)', () => {
    const withThemes = {
      version: '1.0',
      snippets: [currentSnippetRecord()],
      themes: [{ id: 7, name: 'Corporate', config: { background: '#fff' } }],
    };
    expect(normalizeImport(withThemes, { now: FIXED_NOW }).themes).toHaveLength(1);

    const without = { version: '1.0', snippets: [currentSnippetRecord()] };
    expect(normalizeImport(without, { now: FIXED_NOW }).themes).toEqual([]);

    const junkThemes = { version: '1.0', snippets: [currentSnippetRecord()], themes: 'nope' };
    expect(normalizeImport(junkThemes, { now: FIXED_NOW }).themes).toEqual([]);
  });

  it('does not read themes from bare-array or single-object shapes', () => {
    expect(normalizeImport([currentSnippetRecord()], { now: FIXED_NOW }).themes).toEqual([]);
    expect(normalizeImport(currentSnippetRecord(), { now: FIXED_NOW }).themes).toEqual([]);
  });
});

describe('normalizeImport — snippet normalization', () => {
  it('preserves an already-current snippet and does NOT add the imported tag', () => {
    const result = normalizeImport([currentSnippetRecord()], { now: FIXED_NOW });
    const s = result.snippets[0];
    expect(s.id).toBe('s-existing');
    expect(s.name).toBe('Bar chart');
    expect(s.created).toBe('2025-01-02T03:04:05.000Z');
    expect(s.modified).toBe('2025-02-02T03:04:05.000Z');
    expect(s.comment).toBe('hi');
    expect(s.tags).toEqual(['fav']);
    expect(s.datasetRefs).toEqual(['Sales']);
    expect(s.meta).toEqual({ k: 1 });
    expect(s.version).toBe(CURRENT_SNIPPET_VERSION);
    expect(s.tags).not.toContain('imported');
  });

  it('preserves name provenance from our own envelopes, drops anything else', () => {
    const parsed = [
      currentSnippetRecord({ nameSource: 'auto' }),
      currentSnippetRecord({ nameSource: 'user' }),
      { ...currentSnippetRecord(), nameSource: 'bogus' }, // foreign value
      currentSnippetRecord(), // absent
    ];
    const result = normalizeImport(parsed, { now: FIXED_NOW });
    expect(result.snippets.map((s) => s.nameSource)).toEqual([
      'auto',
      'user',
      undefined,
      undefined,
    ]);
  });

  it('fills missing fields on a current snippet with sensible fallbacks', () => {
    const parsed = [{ created: '2025-03-03T03:03:03.000Z', spec: '{"mark":"line"}' }];
    const result = normalizeImport(parsed, { now: FIXED_NOW, makeId: counterIds() });
    const s = result.snippets[0];
    expect(s.id).toBe('id-1');
    expect(s.name).toBe('Untitled');
    expect(s.modified).toBe('2025-03-03T03:03:03.000Z'); // falls back to created
    expect(s.draftSpec).toBe('{"mark":"line"}'); // falls back to spec
    expect(s.comment).toBe('');
    expect(s.tags).toEqual([]); // current shape → not tagged
    expect(s.datasetRefs).toEqual([]);
    expect(s.meta).toEqual({});
  });

  it('maps foreign field names content→spec, draft→draftSpec, createdAt→created', () => {
    const parsed = [
      {
        id: 'foreign-1',
        content: '{"mark":"point"}',
        draft: '{"mark":"point","x":1}',
        createdAt: '2024-12-12T08:00:00.000Z',
      },
    ];
    const result = normalizeImport(parsed, { now: FIXED_NOW });
    const s = result.snippets[0];
    expect(s.spec).toBe('{"mark":"point"}');
    expect(s.draftSpec).toBe('{"mark":"point","x":1}');
    expect(s.created).toBe('2024-12-12T08:00:00.000Z'); // derived from source createdAt
    expect(s.modified).toBe('2024-12-12T08:00:00.000Z');
  });

  it('tags foreign/older snippets "imported" and generates missing timestamps to now', () => {
    const parsed = [{ content: '{"mark":"area"}' }];
    const result = normalizeImport(parsed, { now: FIXED_NOW, makeId: counterIds() });
    const s = result.snippets[0];
    expect(s.tags).toContain('imported');
    expect(s.created).toBe(FIXED_NOW_ISO);
    expect(s.modified).toBe(FIXED_NOW_ISO);
    expect(s.id).toBe('id-1');
  });

  it('does not duplicate the imported tag when already present', () => {
    const parsed = [{ content: '{}', tags: ['imported', 'x'] }];
    const result = normalizeImport(parsed, { now: FIXED_NOW });
    expect(result.snippets[0].tags).toEqual(['imported', 'x']);
  });

  it('treats a bare YYYY-MM-DD created as foreign (tags imported)', () => {
    // A date-only stamp is not the ISO timestamp shape → foreign → tagged + regenerated.
    const parsed = [{ created: '2024-05-05', content: '{}' }];
    const result = normalizeImport(parsed, { now: FIXED_NOW });
    const s = result.snippets[0];
    expect(s.tags).toContain('imported');
    expect(s.created).toBe('2024-05-05'); // derived from the present source timestamp
  });

  it('coerces an object spec/draftSpec into pretty JSON string form', () => {
    const parsed = [
      {
        created: '2025-01-01T00:00:00.000Z',
        spec: { mark: 'bar', encoding: { x: { field: 'a' } } },
      },
    ];
    const result = normalizeImport(parsed, { now: FIXED_NOW });
    const s = result.snippets[0];
    expect(s.spec).toBe(JSON.stringify({ mark: 'bar', encoding: { x: { field: 'a' } } }, null, 2));
    expect(s.draftSpec).toBe(s.spec); // draft falls back to spec
  });

  it('falls back spec to {} when entirely absent', () => {
    const parsed = [{ created: '2025-01-01T00:00:00.000Z' }];
    const result = normalizeImport(parsed, { now: FIXED_NOW });
    expect(result.snippets[0].spec).toBe('{}');
    expect(result.snippets[0].draftSpec).toBe('{}');
  });

  it('is deterministic with injected now and makeId', () => {
    const parsed = [{ content: '{}' }, { content: '{}' }];
    const a = normalizeImport(parsed, { now: FIXED_NOW, makeId: counterIds() });
    const b = normalizeImport(parsed, { now: FIXED_NOW, makeId: counterIds() });
    expect(a).toEqual(b);
    expect(a.snippets.map((s) => s.id)).toEqual(['id-1', 'id-2']);
  });
});

describe('normalizeImport — dataset normalization', () => {
  it('preserves envelope dataset summary fields without re-profiling', () => {
    const parsed = { version: '1.0', snippets: [], datasets: [datasetRecord()] };
    const result = normalizeImport(parsed, { now: FIXED_NOW });
    const d = result.datasets[0];
    expect(d.rowCount).toBe(1);
    expect(d.columnCount).toBe(2);
    expect(d.columns).toEqual(['a', 'b']);
    expect(d.columnTypes).toHaveLength(2);
    expect(d.version).toBe(CURRENT_DATASET_VERSION);
  });

  it('v1→v2: a legacy URL dataset (address in data) imports as an unfetched reference', () => {
    const parsed = {
      version: '1.0',
      snippets: [],
      datasets: [{ id: 1, name: 'Remote', source: 'url', format: 'csv', data: 'https://x/y.csv' }],
    };
    const d = normalizeImport(parsed, { now: FIXED_NOW }).datasets[0];
    expect(d.source).toBe('url');
    expect(d.url).toBe('https://x/y.csv');
    expect(d.data).toBeNull();
    expect(d.fetchedAt).toBeNull();
    expect(d.version).toBe(CURRENT_DATASET_VERSION);
  });

  it('a v2 URL snapshot imports with its data, url, and fetchedAt intact', () => {
    const parsed = {
      version: '1.0',
      snippets: [],
      datasets: [
        {
          id: 1,
          name: 'Remote',
          source: 'url',
          format: 'csv',
          data: 'a,b\n1,2',
          url: 'https://x/y.csv',
          fetchedAt: '2026-06-10T00:00:00.000Z',
        },
      ],
    };
    const d = normalizeImport(parsed, { now: FIXED_NOW }).datasets[0];
    expect(d.data).toBe('a,b\n1,2');
    expect(d.url).toBe('https://x/y.csv');
    expect(d.fetchedAt).toBe('2026-06-10T00:00:00.000Z');
  });

  it('fills gaps with defaults and coerces id to a number', () => {
    const parsed = { version: '1.0', snippets: [], datasets: [{ id: '42', name: 'D' }] };
    const result = normalizeImport(parsed, { now: FIXED_NOW });
    const d = result.datasets[0];
    expect(d.id).toBe(42);
    expect(d.columns).toEqual([]);
    expect(d.columnTypes).toEqual([]);
    expect(d.rowCount).toBeNull();
    expect(d.columnCount).toBeNull();
    expect(d.format).toBe('json');
    expect(d.source).toBe('inline');
    expect(d.comment).toBe('');
    expect(d.size).toBe(0);
    expect(d.created).toBe(FIXED_NOW_ISO);
    expect(d.modified).toBe(FIXED_NOW_ISO);
  });
});

describe('normalizeImport — custom themes', () => {
  const envelope = (themes: unknown[]) => ({
    version: '1.0',
    snippets: [currentSnippetRecord()],
    themes,
  });

  it('preserves a complete record and stamps the current version', () => {
    const [t] = normalizeImport(
      envelope([
        {
          id: 7,
          version: 99,
          name: 'Corporate',
          config: { background: '#fff', font: 'Georgia' },
          created: '2025-03-01T00:00:00.000Z',
          modified: '2025-03-02T00:00:00.000Z',
        },
      ]),
      { now: FIXED_NOW },
    ).themes;
    expect(t.id).toBe(7);
    expect(t.version).toBe(CURRENT_THEME_VERSION);
    expect(t.name).toBe('Corporate');
    expect(t.config).toEqual({ background: '#fff', font: 'Georgia' });
    expect(t.created).toBe('2025-03-01T00:00:00.000Z');
    expect(t.modified).toBe('2025-03-02T00:00:00.000Z');
  });

  it('fills gaps with defaults (junk record)', () => {
    const [t] = normalizeImport(envelope(['junk']), { now: FIXED_NOW }).themes;
    expect(t.id).toBe(0);
    expect(t.version).toBe(CURRENT_THEME_VERSION);
    expect(t.name).toBe('Untitled');
    expect(t.config).toEqual({});
    expect(t.created).toBe(FIXED_NOW_ISO);
    expect(t.modified).toBe(FIXED_NOW_ISO);
  });

  it('coerces a non-object config to {} and derives modified from created', () => {
    const [t] = normalizeImport(
      envelope([{ name: 'Odd', config: [1, 2], created: '2025-03-01T00:00:00.000Z' }]),
      { now: FIXED_NOW },
    ).themes;
    expect(t.config).toEqual({});
    expect(t.modified).toBe('2025-03-01T00:00:00.000Z');
  });
});

describe('normalizeImport — fonts', () => {
  const serializedFont = (family: string) =>
    serializeFontAsset(
      createFontAsset({
        family,
        data: new Uint8Array([1, 2, 3, 4]).buffer,
        format: 'woff2',
        fileName: `${family}.woff2`,
        now: FIXED_NOW,
      }),
    );

  const envelope = (fonts: unknown[]) => ({
    version: '1.0',
    snippets: [currentSnippetRecord()],
    fonts,
  });

  it('decodes envelope fonts back to FontAssets with their bytes intact', () => {
    const { fonts } = normalizeImport(envelope([serializedFont('Brand')]), { now: FIXED_NOW });
    expect(fonts).toHaveLength(1);
    expect(fonts[0].family).toBe('Brand');
    expect(new Uint8Array(fonts[0].data)).toEqual(new Uint8Array([1, 2, 3, 4]));
    expect(fonts[0].size).toBe(4);
  });

  it('drops unusable font records (no data / bad base64) without failing the import', () => {
    const { snippets, fonts } = normalizeImport(
      envelope([serializedFont('Good'), { family: 'NoBytes' }, { family: 'Bad', data: '%%%' }]),
      { now: FIXED_NOW },
    );
    expect(snippets).toHaveLength(1); // the import still succeeds
    expect(fonts.map((f) => f.family)).toEqual(['Good']);
  });

  it('yields no fonts for a bare-array or single-snippet import (no envelope)', () => {
    expect(normalizeImport([currentSnippetRecord()]).fonts).toEqual([]);
    expect(normalizeImport(currentSnippetRecord()).fonts).toEqual([]);
  });
});

describe('dropClashingFonts', () => {
  const font = (family: string, format: FontFormat = 'woff2'): FontAsset =>
    createFontAsset({
      family,
      data: new Uint8Array([1, 2, 3]).buffer,
      format,
      fileName: `${family}.${format}`,
    });

  it('keeps incoming fonts whose family is new', () => {
    const { records, skipped } = dropClashingFonts(['Existing'], [font('Brand'), font('Display')]);
    expect(records.map((f) => f.family)).toEqual(['Brand', 'Display']);
    expect(skipped).toEqual([]);
  });

  it('skips an incoming font whose family already exists (case-insensitive), not rename', () => {
    const { records, skipped } = dropClashingFonts(['Brand'], [font('brand'), font('New')]);
    expect(records.map((f) => f.family)).toEqual(['New']);
    expect(skipped).toEqual(['brand']);
  });

  it('dedupes within the incoming batch (first wins)', () => {
    const { records, skipped } = dropClashingFonts([], [font('Brand'), font('Brand')]);
    expect(records).toHaveLength(1);
    expect(skipped).toEqual(['Brand']);
  });
});

describe('dedupeIncomingNames', () => {
  it('returns names unchanged when there are no collisions', () => {
    const { records, renames } = dedupeIncomingNames(['Other'], [datasetRecord({ name: 'Sales' })]);
    expect(records[0].name).toBe('Sales');
    expect(renames).toEqual([]);
  });

  it('suffixes a collision with an existing library name', () => {
    const { records, renames } = dedupeIncomingNames(['Sales'], [datasetRecord({ name: 'Sales' })]);
    expect(records[0].name).toBe('Sales 2');
    expect(renames).toEqual([{ from: 'Sales', to: 'Sales 2' }]);
  });

  it('reserves names as-you-go so intra-batch dupes become Sales 2, Sales 3', () => {
    const { records, renames } = dedupeIncomingNames(
      ['Sales'],
      [datasetRecord({ name: 'Sales' }), datasetRecord({ name: 'Sales' })],
    );
    expect(records.map((d) => d.name)).toEqual(['Sales 2', 'Sales 3']);
    expect(renames).toEqual([
      { from: 'Sales', to: 'Sales 2' },
      { from: 'Sales', to: 'Sales 3' },
    ]);
  });

  it('matches collisions case-insensitively (Sales vs sales)', () => {
    const { records, renames } = dedupeIncomingNames(['Sales'], [datasetRecord({ name: 'sales' })]);
    expect(records[0].name).toBe('sales 2'); // preserves incoming casing
    expect(renames).toEqual([{ from: 'sales', to: 'sales 2' }]);
  });

  it('only clones records whose name changed', () => {
    const keep = datasetRecord({ name: 'Untouched' });
    const { records } = dedupeIncomingNames(['Sales'], [keep]);
    expect(records[0]).toBe(keep);
  });

  it('works for any named record (custom themes)', () => {
    const { records, renames } = dedupeIncomingNames(
      ['Corporate'],
      [{ name: 'Corporate', config: {} }],
    );
    expect(records[0].name).toBe('Corporate 2');
    expect(renames).toEqual([{ from: 'Corporate', to: 'Corporate 2' }]);
  });
});

describe('reassignCollidingSnippetIds', () => {
  const mk = (id: string): Snippet => ({
    id,
    version: CURRENT_SNIPPET_VERSION,
    name: id,
    created: FIXED_NOW_ISO,
    modified: FIXED_NOW_ISO,
    spec: '{}',
    draftSpec: '{}',
    comment: '',
    tags: [],
    datasetRefs: [],
    meta: {},
  });

  it('keeps a non-colliding id untouched (same object reference)', () => {
    const s = mk('fresh');
    const [out] = reassignCollidingSnippetIds(['existing'], [s], counterIds());
    expect(out).toBe(s);
  });

  it('reassigns an incoming id that collides with an existing snippet', () => {
    const out = reassignCollidingSnippetIds(['dup'], [mk('dup')], counterIds());
    expect(out[0].id).toBe('id-1');
    expect(out[0].name).toBe('dup'); // other fields preserved
  });

  it('gives two incoming snippets sharing one id distinct fresh ids', () => {
    const out = reassignCollidingSnippetIds(['dup'], [mk('dup'), mk('dup')], counterIds());
    expect(out.map((s) => s.id)).toEqual(['id-1', 'id-2']);
  });

  it('skips a generated id that would itself collide with a reserved id', () => {
    // First generated id "id-1" is already reserved → must skip to "id-2".
    const out = reassignCollidingSnippetIds(['dup', 'id-1'], [mk('dup')], counterIds());
    expect(out[0].id).toBe('id-2');
  });

  it('keeps all reassigned ids distinct even when a fresh id matches a later input id', () => {
    // Reservation is left-to-right: the first colliding snippet takes "id-1" and
    // reserves it; the second snippet's existing id is also "id-1", now reserved,
    // so it too is reassigned (→ "id-2"). No two snippets end up sharing an id.
    const out = reassignCollidingSnippetIds(['dup'], [mk('dup'), mk('id-1')], counterIds());
    expect(out[0].id).toBe('id-1');
    expect(out[1].id).toBe('id-2');
    expect(new Set(out.map((s) => s.id)).size).toBe(2);
  });
});

describe('applyDatasetRenamesToSnippets', () => {
  const referencing: Snippet = {
    id: 'r1',
    version: CURRENT_SNIPPET_VERSION,
    name: 'Uses Sales',
    created: FIXED_NOW_ISO,
    modified: FIXED_NOW_ISO,
    spec: JSON.stringify({ data: { name: 'Sales' }, mark: 'bar' }, null, 2),
    draftSpec: JSON.stringify({ data: { name: 'Sales' }, mark: 'line' }, null, 2),
    comment: '',
    tags: [],
    datasetRefs: ['Sales'],
    meta: {},
  };

  it('rewrites spec, draftSpec, and datasetRefs for a referencing snippet', () => {
    const [out] = applyDatasetRenamesToSnippets([referencing], [{ from: 'Sales', to: 'Sales 2' }]);
    expect(dataName(out.spec)).toBe('Sales 2');
    expect(dataName(out.draftSpec)).toBe('Sales 2');
    expect(out.draftSpec).toContain('"line"'); // draft kept its own mark
    expect(out.datasetRefs).toEqual(['Sales 2']);
  });

  it('matches the rename target case-insensitively', () => {
    const lower: Snippet = { ...referencing, datasetRefs: ['Sales'] };
    const [out] = applyDatasetRenamesToSnippets([lower], [{ from: 'sales', to: 'sales 2' }]);
    expect(dataName(out.spec)).toBe('sales 2');
    expect(out.datasetRefs).toEqual(['sales 2']);
  });

  it('leaves a non-referencing snippet untouched (same reference)', () => {
    const unrelated: Snippet = {
      ...referencing,
      id: 'u1',
      spec: JSON.stringify({ data: { name: 'Other' } }, null, 2),
      draftSpec: JSON.stringify({ data: { name: 'Other' } }, null, 2),
      datasetRefs: ['Other'],
    };
    const out = applyDatasetRenamesToSnippets([unrelated], [{ from: 'Sales', to: 'Sales 2' }]);
    expect(out[0]).toBe(unrelated);
  });

  it('returns a fresh array (no aliasing) when there are no renames', () => {
    const input = [referencing];
    const out = applyDatasetRenamesToSnippets(input, []);
    expect(out).not.toBe(input);
    expect(out[0]).toBe(referencing);
  });

  it('applies multiple renames to one snippet', () => {
    const twoRefs: Snippet = {
      ...referencing,
      spec: JSON.stringify(
        { layer: [{ data: { name: 'Sales' } }, { data: { name: 'Regions' } }] },
        null,
        2,
      ),
      draftSpec: JSON.stringify(
        { layer: [{ data: { name: 'Sales' } }, { data: { name: 'Regions' } }] },
        null,
        2,
      ),
      datasetRefs: ['Regions', 'Sales'],
    };
    const [out] = applyDatasetRenamesToSnippets(
      [twoRefs],
      [
        { from: 'Sales', to: 'Sales 2' },
        { from: 'Regions', to: 'Regions 2' },
      ],
    );
    expect(out.datasetRefs).toEqual(['Regions 2', 'Sales 2']);
  });
});
