Skip to content

Commit

Permalink
Merge pull request #4792 from alphagov/flatten-configs
Browse files Browse the repository at this point in the history
Switch to nested (not flattened) configs with stricter checks
  • Loading branch information
colinrotherham authored Mar 8, 2024
2 parents 2554e51 + 3f93724 commit 00daf20
Show file tree
Hide file tree
Showing 14 changed files with 788 additions and 308 deletions.
334 changes: 308 additions & 26 deletions src/govuk/common/index.jsdom.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -30,33 +30,35 @@ describe('Common JS utilities', () => {
}
}

it('flattens a single object', () => {
it('ignores a single object', () => {
const config = mergeConfigs(config1)
expect(config).toEqual({
a: 'antelope',
'c.a': 'camel'
c: { a: 'camel' }
})
})

it('flattens and merges two objects', () => {
it('merges two objects', () => {
const config = mergeConfigs(config1, config2)
expect(config).toEqual({
a: 'aardvark',
b: 'bee',
'c.a': 'cat',
'c.o': 'cobra'
c: { a: 'cat', o: 'cobra' }
})
})

it('flattens and merges three objects', () => {
it('merges three objects', () => {
const config = mergeConfigs(config1, config2, config3)
expect(config).toEqual({
a: 'aardvark',
b: 'bat',
'c.a': 'cat',
'c.o': 'cow',
c: { a: 'cat', o: 'cow' },
d: 'dog',
'e.l.e': 'elephant'
e: {
l: {
e: 'elephant'
}
}
})
})

Expand All @@ -75,10 +77,29 @@ describe('Common JS utilities', () => {
expect(config).toEqual({
a: 'antelope',
b: 'bat',
'c.a': 'camel',
'c.o': 'cow',
c: { a: 'camel', o: 'cow' },
d: 'dog',
e: {
l: {
e: 'elephant'
}
}
})
})

it('prioritises the last parameter provided (different types)', () => {
const config = mergeConfigs(config1, config2, config3, {
c: 'jellyfish', // Replaces top-level object with string
e: { l: 'shark' } // Replaces nested object with string
})
expect(config).toEqual({
a: 'aardvark',
b: 'bat',
c: 'jellyfish',
d: 'dog',
'e.l.e': 'elephant'
e: {
l: 'shark'
}
})
})

Expand All @@ -89,26 +110,283 @@ describe('Common JS utilities', () => {
})

describe('extractConfigByNamespace', () => {
const flattenedConfig = {
a: 'aardvark',
'b.a': 'bat',
'b.e': 'bear',
'b.o': 'boar',
'c.a': 'camel',
'c.o': 'cow',
d: 'dog',
e: 'elephant'
class Component {
/**
* @satisfies {Schema}
*/
static schema = {
properties: {
a: { type: 'string' },
b: { type: 'object' },
c: { type: 'object' },
d: { type: 'string' },
e: { type: 'string' },
f: { type: 'object' }
}
}
}

it('can extract single key-value pairs', () => {
const result = extractConfigByNamespace(flattenedConfig, 'a')
expect(result).toEqual({ a: 'aardvark' })
/** @type {HTMLElement} */
let $element

beforeEach(() => {
document.body.outerHTML = outdent`
<div id="app-example"
data-a="aardvark"
data-b.a="bat"
data-b.e="bear"
data-b.o="boar"
data-c.a="camel"
data-c.o="cow"
data-d="dog"
data-e="element">
</div>
`

$element = document.getElementById('app-example')
})

it('can extract multiple key-value pairs', () => {
const result = extractConfigByNamespace(flattenedConfig, 'b')
it('defaults to empty config for known namespaces only', () => {
const { dataset } = $element

const nonObject1 = extractConfigByNamespace(Component, dataset, 'a')
const nonObject2 = extractConfigByNamespace(Component, dataset, 'd')
const nonObject3 = extractConfigByNamespace(Component, dataset, 'e')

const namespaceKnown = extractConfigByNamespace(Component, dataset, 'f')
const namespaceUnknown = extractConfigByNamespace(
Component,
dataset,
'unknown'
)

// With known namespace but non-object type, default to no config
expect(nonObject1).toEqual(undefined)
expect(nonObject2).toEqual(undefined)
expect(nonObject3).toEqual(undefined)

// With known namespace, default to empty config
expect(namespaceKnown).toEqual({})

// With unknown namespace, default to no config
expect(namespaceUnknown).toEqual(undefined)
})

it('can extract config from key-value pairs', () => {
const result = extractConfigByNamespace(Component, $element.dataset, 'b')
expect(result).toEqual({ a: 'bat', e: 'bear', o: 'boar' })
})

it('can extract config from key-value pairs (with invalid namespace, first)', () => {
document.body.outerHTML = outdent`
<div id="app-example2"
data-i18n
data-i18n.key1="One"
data-i18n.key2="Two"
data-i18n.key3="Three">
</div>
`

const { dataset } = document.getElementById('app-example2')
const result = extractConfigByNamespace(
class Component {
/**
* @satisfies {Schema}
*/
static schema = {
properties: {
i18n: { type: 'object' }
}
}
},
dataset,
'i18n'
)

expect(result).toEqual({ key1: 'One', key2: 'Two', key3: 'Three' })
})

it('can extract config from key-value pairs (with invalid namespace, last)', () => {
document.body.outerHTML = outdent`
<div id="app-example2"
data-i18n.key1="One"
data-i18n.key2="Two"
data-i18n.key3="Three"
data-i18n>
</div>
`

const { dataset } = document.getElementById('app-example2')
const result = extractConfigByNamespace(
class Component {
/**
* @satisfies {Schema}
*/
static schema = {
properties: {
i18n: { type: 'object' }
}
}
},
dataset,
'i18n'
)

expect(result).toEqual({ key1: 'One', key2: 'Two', key3: 'Three' })
})

it('handles when both shallow and deep keys are set (namespace collision)', () => {
document.body.outerHTML = outdent`
<div id="app-example"
data-a="aardvark"
data-b="bat"
data-c="jellyfish"
data-c.a="cat"
data-c.o="cow"
data-d="dog"
data-e="element"
data-f.e="elk"
data-f.e.l="elephant">
</div>
`

const { dataset } = document.getElementById('app-example')
const result = extractConfigByNamespace(Component, dataset, 'c')

expect(result).toEqual({ a: 'cat', o: 'cow' })
})

it('handles when both shallow and deep keys are set (namespace collision + key collision in namespace)', () => {
document.body.outerHTML = outdent`
<div id="app-example"
data-a="aardvark"
data-b="bat"
data-c.c="crow"
data-c="jellyfish"
data-c.a="cat"
data-c.o="cow"
data-d="dog"
data-e="element"
data-f.e="elk"
data-f.e.l="elephant">
</div>
`

const { dataset } = document.getElementById('app-example')
const result = extractConfigByNamespace(Component, dataset, 'c')

expect(result).toEqual({ a: 'cat', c: 'crow', o: 'cow' })
})

it('handles when both shallow and deep keys are set (namespace collision + key collision in namespace after shallow)', () => {
document.body.outerHTML = outdent`
<div id="app-example"
data-a="aardvark"
data-b="bat"
data-c="jellyfish"
data-c.a="cat"
data-c.c="crow"
data-c.o="cow"
data-d="dog"
data-e="element"
data-f.e="elk"
data-f.e.l="elephant">
</div>
`

const { dataset } = document.getElementById('app-example')
const result = extractConfigByNamespace(Component, dataset, 'c')

expect(result).toEqual({ a: 'cat', c: 'crow', o: 'cow' })
})

it('handles when both shallow and deep keys are set (deeper collision)', () => {
document.body.outerHTML = outdent`
<div id="app-example"
data-a="aardvark"
data-b="bat"
data-c="jellyfish"
data-c.a="cat"
data-c.o="cow"
data-d="dog"
data-f.e="elk"
data-f.e.l="elephant">
</div>
`

const { dataset } = document.getElementById('app-example')
const result = extractConfigByNamespace(Component, dataset, 'f')

expect(result).toEqual({ e: { l: 'elephant' } })
})

it('can handle multiple levels of nesting', () => {
document.body.outerHTML = outdent`
<div id="app-example2"
data-i18n.key1="This, That"
data-i18n.key2.one="The"
data-i18n.key2.other="Other">
</div>
`

const { dataset } = document.getElementById('app-example2')
const result = extractConfigByNamespace(
class Component {
/**
* @satisfies {Schema}
*/
static schema = {
properties: {
i18n: { type: 'object' }
}
}
},
dataset,
'i18n'
)

expect(result).toEqual({
key1: 'This, That',
key2: {
one: 'The',
other: 'Other'
}
})
})

it('can handle multiple levels of nesting (prioritises the last parameter provided)', () => {
document.body.outerHTML = outdent`
<div id="app-example2"
data-i18n.key1.one="This"
data-i18n.key1.other="That"
data-i18n.key2.one="The"
data-i18n.key2.other="Other"
data-i18n.key1="This, That"
data-i18n.key2="The Other">
</div>
`

const { dataset } = document.getElementById('app-example2')
const result = extractConfigByNamespace(
class Component {
/**
* @satisfies {Schema}
*/
static schema = {
properties: {
i18n: { type: 'object' }
}
}
},
dataset,
'i18n'
)

expect(result).toEqual({
key1: 'This, That',
key2: 'The Other'
})
})
})

describe('isSupported', () => {
Expand Down Expand Up @@ -219,3 +497,7 @@ describe('Common JS utilities', () => {
})
})
})

/**
* @typedef {import('./index.mjs').Schema} Schema
*/
Loading

0 comments on commit 00daf20

Please sign in to comment.