import { default as fetch, Response } from 'node-fetch' import { afterAll, assert, beforeAll, describe, expect, it, vi } from 'vitest' import { CollectionModel, ComponentModel, LibraryModel, Style, Transport, Version, VersionModel } from '../src/index.js' const defaultTheme = Style.Rule({ details: { id: 'default-theme', title: 'Default theme', slug: 'default' }, type: 'theme' }) const sampleCombo = { id: 'sample-combo', refId: '1234', fillId: '1234', decorationId: '1234', isDefault: true, paletteId: '1234', spacingId: '1234', typographyId: '1234' } const now = new Date() const draftA0: Version = { id: 'A', libraryId: 'lib', componentId: '123', revision: 0, name: 'DraftA', status: 'draft', view: `
A0
`, model: `
A0
`, orderIdx: '', datasourceIds: [], modifiedAt: now, createdAt: now, classList: ['-theme--default'] } const savedA0: Version = { ...draftA0, status: 'saved' } const publishedA0: Version = { ...draftA0, status: 'published' } const draftA1: Version = { ...draftA0, revision: 1 } const draftWithCombo: Version = { ...draftA0, classList: ['-theme--default', '-use--a-new-combo'] } const getUnique = (v: any, i: any, a: any) => a.indexOf(v) === i const getUniqueIds = (versions: VersionModel[]) => versions.map((v) => v.getId()).filter(getUnique) const date = new Date(2000, 1, 1, 1) describe('Versions', () => { beforeAll(() => { //vi.useFakeTimers() vi.setSystemTime(date) }) afterAll(() => { //vi.useRealTimers() }) describe('addVersion', () => { it('should add new versions', () => { const component = new ComponentModel().set({ id: '123', libraryId: 'lib' }) component.addVersion() assert.equal(component.versions[0].component, component) assert.equal(component.versions.length, 1) assert.equal(component.versions[0].id.length, 10) assert.equal(component.versions[0].orderIdx.length > 0, true) assert.equal(component.versions[0].model, '') assert.equal(component.versions[0].view, '
') assert.equal(component.versions[0].status, 'draft') }) it('should set values', () => { const component = new ComponentModel().set({ id: '123', libraryId: 'lib' }) component.addVersion({ name: 'abc' }) assert.equal(component.versions.length, 1) assert.equal(component.versions[0].name, 'abc') }) it('should add new versions after previous ones', () => { const component = new ComponentModel().set({ id: '123', libraryId: 'lib' }) component.addVersion() component.addVersion() component.addVersion() assert.equal(component.versions.length, 3) assert.equal(getUniqueIds(component.versions).length, 3) }) }) describe('setVersionDirty', () => { it('should not do anything to draft', () => { const component = new ComponentModel().set({ id: '123', libraryId: 'lib' }) component.versions.setItems([draftA0]) component.versions[0].getDraft() assert.deepEqual(component.versions.export(), [draftA0]) }) it('should bump saved revision', () => { const component = new ComponentModel().set({ id: '123', libraryId: 'lib' }) component.versions.setItems([savedA0]) component.versions[0].getDraft() assert.equal(component.versions.length, 2) assert.equal(component.findVersion('A', ['draft']).status, 'draft') const added = component.versions[0] assert.equal(getUniqueIds(component.versions).length, 2) assert.equal(added.revision, 1) assert.equal(added.id, 'A') assert.equal(added.status, 'draft') }) }) describe('setVersionTheme', () => { it('should update draft with theme id', () => { const component = new ComponentModel().set({ id: '123', libraryId: 'lib' }) component.library = new LibraryModel({ name: 'testing breakpoints' }) component.library.stylesheets.add({}).rules.setItems(component.library.stylesheet.source) component.versions.setItems([draftA0]) const newVersion = component.versions[0].setClassList( Style.ClassList.setTheme(component.versions[0].classList, defaultTheme) ) assert.equal(newVersion.revision, 0) assert.equal(newVersion.getThemeSlug(), defaultTheme.details.slug) }) it('should return a new version with theme id', () => { const component = new ComponentModel().set({ id: '123', libraryId: 'lib' }) component.library = new LibraryModel({ name: 'testing breakpoints' }) component.library.stylesheets.add({}).rules.setItems(component.library.stylesheet.source) component.versions.setItems([savedA0]) const newVersion = component.versions[0].setClassList( Style.ClassList.setTheme(component.versions[0].classList, defaultTheme) ) assert.equal(newVersion.revision, 1) assert.equal(newVersion.getThemeSlug(), defaultTheme.details.slug) }) it('should remove any combos', () => { const component = new ComponentModel().set({ id: '123', libraryId: 'lib' }) component.library = new LibraryModel({ name: 'testing breakpoints' }) component.library.stylesheets.add({}).rules.setItems(component.library.stylesheet.source) component.versions.setItems([draftWithCombo]) const newVersion = component.versions[0].setClassList( Style.ClassList.setTheme(component.versions[0].classList, defaultTheme) ) assert.deepEqual(newVersion.classList, ['-theme--default']) }) }) describe('commitVersionData', () => { it('should update draft if content changed', () => { const component = new ComponentModel().set({ id: '123', libraryId: 'lib' }) component.versions.setItems([draftA0]) component.versions[0].commitData({ view: '
A0!
', model: '
A0!
' }) assert.equal(component.versions.length, 1) assert.equal(component.versions[0].view, '
A0!
') assert.equal(component.versions[0].model, '
A0!
') assert.equal(component.versions[0].revision, 0) assert.notEqual(component.versions[0].modifiedAt, draftA0.modifiedAt) }) it('should not update draft if content changed', () => { const component = new ComponentModel().set({ id: '123', libraryId: 'lib' }) component.versions.setItems([draftA0]) component.versions[0].commitData({ view: `
A0
`, model: `
A0
` }) assert.equal(component.versions.length, 1) assert.equal(component.versions[0].view, `
A0
`) assert.equal(component.versions[0].model, `
A0
`) assert.equal(component.versions[0].revision, 0) assert.equal(component.versions[0].modifiedAt, draftA0.modifiedAt) }) it('should create draft if content changed', () => { const component = new ComponentModel().set({ id: '123', libraryId: 'lib' }) component.versions.setItems([savedA0]) component.versions[0].commitData({ view: `
A1
`, model: `
A1
` }) assert.equal(component.findVersion('A').revision, 1) assert.equal(component.findVersion('A').status, 'draft') assert.equal(component.findVersion('A').view, `
A1
`) assert.equal(component.findVersion('A').model, `
A1
`) assert.notEqual(component.versions[0].modifiedAt, draftA0.modifiedAt) assert.equal(component.versions.length, 2) assert.equal(getUniqueIds(component.versions).length, 2) }) it('should not bump revision if content hasnt changed', () => { const component = new ComponentModel().set({ id: '123', libraryId: 'lib' }) component.versions.setItems([savedA0]) component.versions[0].commitData({ view: `
A0
`, model: `
A0
` }) assert.equal(component.findVersion('A').status, 'saved') assert.deepEqual(component.versions.export(), [savedA0]) }) }) describe('saveVersionData', () => { it('should bump saved revision', () => { const component = new ComponentModel().set({ id: '123', libraryId: 'lib' }) component.versions.setItems([savedA0]) component.versions[0].saveData({ view: `
A1
`, model: `
A1
` }) assert.equal(component.findVersion('A').status, 'saved') const added = component.versions[0] assert.equal(component.versions.length, 2) assert.equal(getUniqueIds(component.versions).length, 2) assert.equal(added.revision, 1) assert.equal(added.id, 'A') assert.equal(added.model, `
A1
`) assert.equal(added.view, `
A1
`) assert.equal(added.status, 'saved') }) it('should not re-save unless data has changed', () => { const component = new ComponentModel().set({ id: '123', libraryId: 'lib' }) component.versions.setItems([savedA0]) component.versions[0].saveData({ view: `
A0
`, model: `
A0
` }) assert.equal(component.findVersion('A').status, 'saved') assert.deepEqual(component.versions.export(), [savedA0]) }) it('should save draft even if data has not changed', () => { const component = new ComponentModel().set({ id: '123', libraryId: 'lib' }) component.versions.setItems([draftA0]) component.versions[0].saveData({ view: `
A0
`, model: `
A0
` }) assert.equal(component.findVersion('A').status, 'saved') assert.equal(component.versions.length, 2) assert.equal(getUniqueIds(component.versions).length, 2) const added = component.versions[0] assert.notEqual(added.status, draftA0.status) assert.notEqual(added.modifiedAt, draftA0.modifiedAt) assert.equal(added.revision, 0) assert.equal(added.id, 'A') assert.equal(added.model, `
A0
`) assert.equal(added.view, `
A0
`) assert.equal(added.status, 'saved') }) it('should save deleted revision', () => { const component = new ComponentModel().set({ id: '123' }) component.versions.setItems([ { ...draftA0, deletedAt: new Date() } ]) component.versions[0].saveData({ view: `
A0
`, model: `
A0
` }) assert.equal(component.findVersion('A').status, 'draft') assert.equal(!!component.findVersion('A').deletedAt, true) assert.equal(component.versions.length, 1) assert.equal(getUniqueIds(component.versions).length, 1) }) }) describe('stageVersion', () => { it('should stage version', () => { const component = new ComponentModel().set({ id: '123', libraryId: 'lib' }) component.versions.setItems([draftA0]) component.versions[0].stage() assert.equal(component.findVersion('A').status, 'staged') assert.equal(component.versions.length, 2) assert.equal(getUniqueIds(component.versions).length, 2) }) }) describe('deleteVersion', () => { it('should discard draft on deletion', () => { const component = new ComponentModel().set({ id: '123', libraryId: 'lib' }) component.versions.setItems([draftA0]) component.versions[0].archive() assert.equal(component.versions.length, 0) }) it('should delete saved, ignoring old draft', () => { const component = new ComponentModel().set({ id: '123', libraryId: 'lib' }) component.versions.setItems([savedA0, draftA0]) component.versions[1].archive() assert.equal(component.findVersion('A').status, 'saved') assert.notEqual(component.findVersion('A').deletedAt, null) assert.equal(component.versions.length, 2) assert.equal(getUniqueIds(component.versions).length, 2) }) it('should discard draft and delete saved', () => { const component = new ComponentModel().set({ id: '123', libraryId: 'lib' }) component.versions.setItems([draftA1, savedA0]) component.versions[0].archive() assert.equal(component.findVersion('A').status, 'saved') assert.notEqual(component.findVersion('A').deletedAt, null) assert.equal(component.versions.length, 1) assert.equal(getUniqueIds(component.versions).length, 1) }) it('should ignore discard draft and delete saved', () => { const component = new ComponentModel().set({ id: '123', libraryId: 'lib' }) component.versions.setItems([draftA1, publishedA0, savedA0]) component.versions[0].archive() assert.equal(component.findVersion('A').status, 'published') assert.equal(component.findVersion('A', ['saved']).status, 'saved') assert.equal(component.findVersion('A', ['saved']).revision, 0) assert.notEqual(component.findVersion('A', ['saved']).deletedAt, null) assert.equal(component.versions.length, 2) assert.equal(getUniqueIds(component.versions).length, 2) }) it('should ignore old draft and delete saved', () => { const component = new ComponentModel().set({ id: '123', libraryId: 'lib' }) component.versions.setItems([draftA0, publishedA0, savedA0]) component.versions[0].archive() assert.equal(component.findVersion('A').status, 'published') assert.equal(component.findVersion('A', ['saved']).status, 'saved') assert.equal(component.findVersion('A', ['saved']).revision, 0) assert.notEqual(component.findVersion('A', ['saved']).deletedAt, null) assert.equal(component.versions.length, 3) assert.equal(getUniqueIds(component.versions).length, 3) }) }) describe('publishVersion', () => { it('does not stage automatically', () => { const component = new ComponentModel().set({ id: '123', libraryId: 'lib' }) component.versions.setItems([draftA0]) component.versions[0].publish() assert.equal(component.versions.length, 2) assert.equal(getUniqueIds(component.versions).length, 2) assert.equal(component.findVersion('A', ['staged']), null) assert.equal(component.findVersion('A').status, 'published') }) }) describe('saveVersions', () => { it('should not save drafts', () => { const spy = vi.spyOn(Transport, 'fetch') const component = new ComponentModel().set({ id: '123', libraryId: 'lib' }) component.versions.snapshot() component.addVersion({ view: 'abc' }) component.saveVersions() expect(spy).toHaveBeenCalledTimes(0) }) it('should save saved', () => { const spy = vi.spyOn(Transport, 'fetch') // @ts-ignore spy.mockImplementation(async (...args: Parameters) => { return new Response('{}') }) const component = new ComponentModel().set({ id: '123', libraryId: 'lib' }) component.versions.snapshot() component.addVersion({ view: 'abc', status: 'saved' }) component.saveVersions() expect(spy).toHaveBeenCalledTimes(1) component.addVersion({ view: 'abc', status: 'saved' }) component.saveVersions() component.addVersion({ view: 'abc', status: 'saved' }) component.saveVersions() expect(spy).toHaveBeenCalledTimes(3) expect(component.versions.length).toEqual(3) }) it('should stage versions', () => { const spy = vi.spyOn(Transport, 'fetch') // @ts-ignore spy.mockImplementation(async (...args: Parameters) => { return new Response('{}') }) const component = new ComponentModel().set({ id: '123', libraryId: 'lib' }) const version = component.addVersion({ id: 'a', view: 'abc', status: 'saved' }) component.versions.snapshot() version.stage() component.addVersion({ id: 'b', view: 'cde', status: 'saved' }) component.saveVersions() expect(spy).toHaveBeenCalledTimes(2) expect(component.versions.map((v) => [v.id, v.status])).toEqual([ ['a', 'staged'], ['a', 'saved'], ['b', 'saved'] ]) expect(component.getVersions().map((v) => [v.id, v.status])).toEqual([ ['a', 'staged'], ['b', 'saved'] ]) component.saveVersions() expect(component.stagedAt).toBeUndefined() component.aggregateVersionData() expect(component.stagedAt).toEqual(version.modifiedAt) expect(spy).toHaveBeenCalledTimes(2) }) it('should unstage versions', () => { const spy = vi.spyOn(Transport, 'fetch') // @ts-ignore spy.mockImplementation(async (...args: Parameters) => { return new Response('{}') }) const component = new ComponentModel().set({ id: '123' }) const version1 = component.addVersion({ id: 'a', view: 'abc', status: 'staged' }) const version2 = component.addVersion({ id: 'b', view: 'cde', status: 'saved' }) component.versions.snapshot() version1.unstage() version2.archive() component.saveVersions() expect(spy).toHaveBeenCalledTimes(2) expect(component.versions.map((v) => [v.id, v.status, v.deletedAt])).toEqual([ ['a', 'staged', date], ['b', 'saved', date] ]) version1.stage() version2.restore() component.saveVersions() expect(component.versions.map((v) => [v.id, v.status, v.deletedAt])).toEqual([ ['a', 'staged', null], ['b', 'saved', null] ]) expect(spy).toHaveBeenCalledTimes(4) }) }) describe('getVersionForWidth', () => { let component: ComponentModel let collection: CollectionModel beforeAll(() => { const library = new LibraryModel({ name: 'testing breakpoints' }) library.stylesheets.add({}).rules.setItems(library.stylesheet.source) collection = library.collections.new({ name: 'Collection' }) component = collection.components.new({ id: '345', name: 'Test' }) component.addVersion({ name: 'large', breakpointIds: ['large'] }) component.addVersion({ name: 'medium', breakpointIds: ['medium'] }) component.addVersion({ name: 'multi', breakpointIds: ['large', 'medium'] }) }) it('should return version matching width (least number of breakpoints)', () => { expect(component.getVersionForWidth(800).name).toEqual('medium') }) it('should return version matching name and width', () => { expect(component.getVersionForWidth(1000, 'multi').name).toEqual('multi') }) it('should return version matching name and width (least number of breakpoints)', () => { expect(component.getVersionForWidth(1000, 'large').name).toEqual('large') }) it('should return version matching width (even if name is not matching)', () => { expect(component.getVersionForWidth(1000, 'medium').name).toEqual('large') }) it('should return closest width version if name and width not matching', () => { expect(component.getVersionForWidth(500, 'not matching name').name).toEqual('medium') }) it('should return closest width if width not matching and no name specified ', () => { expect(component.getVersionForWidth(500).name).toEqual('medium') }) it('should return global version if no specific width version is found and global exists', () => { const newComponent = collection.components.construct({ id: '456' }) newComponent.addVersion({ name: 'global', breakpointIds: null }) newComponent.addVersion({ name: 'medium', breakpointIds: ['medium'] }) expect(newComponent.getVersionForWidth(1300).name).toEqual('global') }) it('should not use global version if mroe specific exists with infinity', () => { const newComponent = collection.components.construct({ id: '456' }) newComponent.addVersion({ name: 'global', breakpointIds: null }) newComponent.addVersion({ name: 'xxlarge', breakpointIds: ['xxlarge'] }) expect(newComponent.getVersionForWidth(Infinity).name).toEqual('xxlarge') }) it('should return null if there are no versions in component', () => { const newComponent = new ComponentModel() expect(newComponent.getVersionForWidth(1300)).toBeNull() }) }) describe('generateResponsiveHTML', () => { let component: ComponentModel let collection: CollectionModel let breakpoints: Style.Rule<'breakpoint'>[] beforeAll(() => { const library = new LibraryModel({ name: 'testing breakpoints' }) library.stylesheets.add({}).rules.setItems(library.stylesheet.source) collection = library.collections.new({ name: 'Collection' }) breakpoints = Style.Set.filterByType(library.stylesheet.rules, 'breakpoint') component = collection.components.new({ id: '345', name: 'Test' }) component.addVersion({ name: 'large', breakpointIds: ['large'], view: 'LARGE', classList: ['-theme--christmas'] }) component.addVersion({ name: 'medium', breakpointIds: ['medium'], view: 'MEDIUM', classList: ['-theme--default', '-use--test'] }) component.addVersion({ name: 'multi', breakpointIds: ['large', 'medium', 'small'], view: 'MULTI' }) component.addVersion({ name: 'unspecific', breakpointIds: ['large', 'medium'], view: 'Unspecific' }) }) it('should produce html', () => { expect(component.getBundles()[0].aggregate('draft').getHTML()).toEqual( `
LARGE
MEDIUM
MULTI
` ) }) }) })