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: ``,
model: ``,
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: '',
model: ''
})
assert.equal(component.versions.length, 1)
assert.equal(component.versions[0].view, '')
assert.equal(component.versions[0].model, '')
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: ``,
model: ``
})
assert.equal(component.versions.length, 1)
assert.equal(component.versions[0].view, ``)
assert.equal(component.versions[0].model, ``)
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: ``,
model: ``
})
assert.equal(component.findVersion('A').revision, 1)
assert.equal(component.findVersion('A').status, 'draft')
assert.equal(component.findVersion('A').view, ``)
assert.equal(component.findVersion('A').model, ``)
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: ``,
model: ``
})
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: ``,
model: ``
})
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, ``)
assert.equal(added.view, ``)
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: ``,
model: ``
})
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: ``,
model: ``
})
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, ``)
assert.equal(added.view, ``)
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: ``,
model: ``
})
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(
`
`
)
})
})
})