Skip to content

Commit

Permalink
Add GOVUKFrontendNotSupported error
Browse files Browse the repository at this point in the history
Allows components to indicate they didn't inistantiate because GOV.UK Frontend is not supported
  • Loading branch information
romaricpascal committed Aug 1, 2023
1 parent 2e57cd6 commit a533c7e
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 5 deletions.
2 changes: 2 additions & 0 deletions packages/govuk-frontend/src/govuk/all.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { NotificationBanner } from './components/notification-banner/notificatio
import { Radios } from './components/radios/radios.mjs'
import { SkipLink } from './components/skip-link/skip-link.mjs'
import { Tabs } from './components/tabs/tabs.mjs'
import { GOVUKFrontendError } from './errors/index.mjs'

/**
* Initialise all components
Expand Down Expand Up @@ -103,6 +104,7 @@ export {
Checkboxes,
ErrorSummary,
ExitThisPage,
GOVUKFrontendError,
Header,
NotificationBanner,
Radios,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { mergeConfigs, extractConfigByNamespace } from '../../common/index.mjs'
import { normaliseDataset } from '../../common/normalise-dataset.mjs'
import { GOVUKFrontendNotSupportedError } from '../../errors/index.mjs'
import { I18n } from '../../i18n.mjs'

/**
Expand Down Expand Up @@ -113,7 +114,11 @@ export class Accordion {
* @param {AccordionConfig} [config] - Accordion config
*/
constructor ($module, config) {
if (!($module instanceof HTMLElement) || !document.body.classList.contains('govuk-frontend-supported')) {
if (!document.body.classList.contains('govuk-frontend-supported')) {
throw new GOVUKFrontendNotSupportedError()
}

if (!($module instanceof HTMLElement)) {
return this
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,28 @@ describe('/components/accordion', () => {
)
})
})

describe('Errors at instantiation', () => {
let examples

beforeAll(async () => {
examples = await getExamples('accordion')
})

it('throws when GOV.UK Frontend is not supported', async () => {
await expect(
renderAndInitialise(page, 'accordion', {
params: examples.default,
beforeInitialisation () {
document.body.classList.remove('govuk-frontend-supported')
}
})
).rejects.toEqual({
name: 'GOVUKFrontendNotSupportedError',
message: 'GOV.UK Frontend is not supported in this browser'
})
})
})
})
})
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ describe('GOV.UK Frontend', () => {
'Checkboxes',
'ErrorSummary',
'ExitThisPage',
'GOVUKFrontendError',
'Header',
'NotificationBanner',
'Radios',
Expand Down
33 changes: 33 additions & 0 deletions packages/govuk-frontend/src/govuk/errors/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* A base class for `Error`s thrown by GOV.UK Frontend.
*
* It is meant to be extended into specific types of errors
* to be thrown by our code.
*
* @example
* ```js
* class MissingRootError extends GOVUKFrontendError {
* // Setting an explicit name is important as extending the class will not
* // set a new `name` on the subclass. The `name` property is important
* // to ensure intelligible error names even if the class name gets
* // mangled by a minifier
* name = "MissingRootError"
* }
* ```
* @abstract
*/
export class GOVUKFrontendError extends Error {
name = 'GOVUKFrontendError'
}

/**
* Indicates that GOV.UK Frontend is not supported
*/
export class GOVUKFrontendNotSupportedError extends GOVUKFrontendError {
name = 'GOVUKFrontendNotSupportedError'

/** */
constructor () {
super('GOV.UK Frontend is not supported in this browser')
}
}
31 changes: 31 additions & 0 deletions packages/govuk-frontend/src/govuk/errors/index.unit.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { GOVUKFrontendError, GOVUKFrontendNotSupportedError } from './index.mjs'

describe('errors', () => {
describe('GOVUKFrontendError', () => {
it('allows subclasses to set a custom name', () => {
class CustomError extends GOVUKFrontendError {
name = 'CustomName'
}

expect(new CustomError().name).toBe('CustomName')
})
})

describe('GOVUKFrontendNotSupportedError', () => {
it('is an instance of GOVUKFrontendError', () => {
expect(new GOVUKFrontendNotSupportedError()).toBeInstanceOf(
GOVUKFrontendError
)
})
it('has its own name set', () => {
expect(new GOVUKFrontendNotSupportedError().name).toBe(
'GOVUKFrontendNotSupportedError'
)
})
it('provides meaningfull feedback to users', () => {
expect(new GOVUKFrontendNotSupportedError().message).toBe(
'GOV.UK Frontend is not supported in this browser'
)
})
})
})
22 changes: 18 additions & 4 deletions shared/helpers/puppeteer.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,25 @@ async function renderAndInitialise (page, componentName, options) {
}

// Run a script to init the JavaScript component
await componentRootHandle.evaluate(async ($module, exportName, options) => {
// Puppeteer returns very little information on errors thrown during `evaluate`,
// only a `name` that maps to the error class (and not its `name` property,
// which means we get a mangled value).
// As a workaround, we can gather and `return` the values we need from inside the browser,
// and throw them when back in Jest (to keep them triggering a Promise rejection)
const error = await componentRootHandle.evaluate(async ($module, exportName, options) => {
const namespace = await import('govuk-frontend')
/* eslint-disable-next-line no-new */
new namespace[exportName]($module, options.config)
}, componentNameToClassName(componentName), options, options.beforeInitialisation)

try {
/* eslint-disable-next-line no-new */
new namespace[exportName]($module, options.config)
} catch ({ name, message }) {
return { name, message }
}
}, componentNameToClassName(componentName), options)

if (error) {
throw error
}

return page
}
Expand Down

0 comments on commit a533c7e

Please sign in to comment.