Skip to content

Commit

Permalink
Merge pull request #5272 from alphagov/init-mult
Browse files Browse the repository at this point in the history
Prevent multiple initialisations of a single component instance
  • Loading branch information
patrickpatrickpatrick authored Sep 16, 2024
2 parents 0a70233 + 49f0185 commit 5987b57
Show file tree
Hide file tree
Showing 26 changed files with 311 additions and 15 deletions.
15 changes: 15 additions & 0 deletions packages/govuk-frontend/src/govuk/common/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,21 @@ export function setFocus($element, options = {}) {
$element.focus()
}

/**
* Checks if component is already initialised
*
* @internal
* @param {Element} $module - HTML element to be checked
* @param {string} moduleName - name of component module
* @returns {boolean} Whether component is already initialised
*/
export function isInitialised($module, moduleName) {
return (
$module instanceof HTMLElement &&
$module.hasAttribute(`data-${moduleName}-init`)
)
}

/**
* Checks if GOV.UK Frontend is supported on this page
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export class Accordion extends GOVUKFrontendComponent {
* @param {AccordionConfig} [config] - Accordion config
*/
constructor($module, config = {}) {
super()
super($module)

if (!($module instanceof HTMLElement)) {
throw new ElementError({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint-disable no-new */

const {
goToExample,
render,
Expand Down Expand Up @@ -712,6 +714,21 @@ describe('/components/accordion', () => {
})
})

it('throws when initialised twice', async () => {
await expect(
render(page, 'accordion', examples.default, {
async afterInitialisation($module) {
const { Accordion } = await import('govuk-frontend')
new Accordion($module)
}
})
).rejects.toMatchObject({
name: 'InitError',
message:
'Root element (`$module`) already initialised (`govuk-accordion`)'
})
})

it('throws when $module is not set', async () => {
await expect(
render(page, 'accordion', examples.default, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export class Button extends GOVUKFrontendComponent {
* @param {ButtonConfig} [config] - Button config
*/
constructor($module, config = {}) {
super()
super($module)

if (!($module instanceof HTMLElement)) {
throw new ElementError({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint-disable no-new */

const { render } = require('@govuk-frontend/helpers/puppeteer')
const { getExamples } = require('@govuk-frontend/lib/components')

Expand Down Expand Up @@ -329,6 +331,21 @@ describe('/components/button', () => {
})
})

it('throws when initialised twice', async () => {
await expect(
render(page, 'button', examples.default, {
async afterInitialisation($module) {
const { Button } = await import('govuk-frontend')
new Button($module)
}
})
).rejects.toMatchObject({
name: 'InitError',
message:
'Root element (`$module`) already initialised (`govuk-button`)'
})
})

it('throws when $module is not set', async () => {
await expect(
render(page, 'button', examples.default, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export class CharacterCount extends GOVUKFrontendComponent {
* @param {CharacterCountConfig} [config] - Character count config
*/
constructor($module, config = {}) {
super()
super($module)

if (!($module instanceof HTMLElement)) {
throw new ElementError({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint-disable no-new */

const { setTimeout } = require('timers/promises')

const { render } = require('@govuk-frontend/helpers/puppeteer')
Expand Down Expand Up @@ -812,6 +814,21 @@ describe('Character count', () => {
})
})

it('throws when initialised twice', async () => {
await expect(
render(page, 'character-count', examples.default, {
async afterInitialisation($module) {
const { CharacterCount } = await import('govuk-frontend')
new CharacterCount($module)
}
})
).rejects.toMatchObject({
name: 'InitError',
message:
'Root element (`$module`) already initialised (`govuk-character-count`)'
})
})

it('throws when $module is not set', async () => {
await expect(
render(page, 'character-count', examples.default, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export class Checkboxes extends GOVUKFrontendComponent {
* @param {Element | null} $module - HTML element to use for checkboxes
*/
constructor($module) {
super()
super($module)

if (!($module instanceof HTMLElement)) {
throw new ElementError({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint-disable no-new */

const {
goToExample,
getAttribute,
Expand Down Expand Up @@ -367,6 +369,21 @@ describe('Checkboxes', () => {
})
})

it('throws when initialised twice', async () => {
await expect(
render(page, 'checkboxes', examples.default, {
async afterInitialisation($module) {
const { Checkboxes } = await import('govuk-frontend')
new Checkboxes($module)
}
})
).rejects.toMatchObject({
name: 'InitError',
message:
'Root element (`$module`) already initialised (`govuk-checkboxes`)'
})
})

it('throws when $module is not set', async () => {
await expect(
render(page, 'checkboxes', examples.default, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class ErrorSummary extends GOVUKFrontendComponent {
* @param {ErrorSummaryConfig} [config] - Error summary config
*/
constructor($module, config = {}) {
super()
super($module)

if (!($module instanceof HTMLElement)) {
throw new ElementError({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint-disable no-new */

const { goToExample, render } = require('@govuk-frontend/helpers/puppeteer')
const { getExamples } = require('@govuk-frontend/lib/components')

Expand Down Expand Up @@ -236,6 +238,21 @@ describe('Error Summary', () => {
})
})

it('throws when initialised twice', async () => {
await expect(
render(page, 'error-summary', examples.default, {
async afterInitialisation($module) {
const { ErrorSummary } = await import('govuk-frontend')
new ErrorSummary($module)
}
})
).rejects.toMatchObject({
name: 'InitError',
message:
'Root element (`$module`) already initialised (`govuk-error-summary`)'
})
})

it('throws when $module is not set', async () => {
await expect(
render(page, 'error-summary', examples.default, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export class ExitThisPage extends GOVUKFrontendComponent {
* @param {ExitThisPageConfig} [config] - Exit This Page config
*/
constructor($module, config = {}) {
super()
super($module)

if (!($module instanceof HTMLElement)) {
throw new ElementError({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint-disable no-new */

const { setTimeout } = require('timers/promises')

const { goToExample, render } = require('@govuk-frontend/helpers/puppeteer')
Expand Down Expand Up @@ -231,6 +233,21 @@ describe('/components/exit-this-page', () => {
})
})

it('throws when initialised twice', async () => {
await expect(
render(page, 'exit-this-page', examples.default, {
async afterInitialisation($module) {
const { ExitThisPage } = await import('govuk-frontend')
new ExitThisPage($module)
}
})
).rejects.toMatchObject({
name: 'InitError',
message:
'Root element (`$module`) already initialised (`govuk-exit-this-page`)'
})
})

it('throws when $module is not set', async () => {
await expect(
render(page, 'exit-this-page', examples.default, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export class Header extends GOVUKFrontendComponent {
* @param {Element | null} $module - HTML element to use for header
*/
constructor($module) {
super()
super($module)

if (!$module) {
throw new ElementError({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint-disable no-new */

const { render } = require('@govuk-frontend/helpers/puppeteer')
const { getExamples } = require('@govuk-frontend/lib/components')
const { KnownDevices } = require('puppeteer')
Expand Down Expand Up @@ -181,6 +183,21 @@ describe('Header navigation', () => {
})
})

it('throws when initialised twice', async () => {
await expect(
render(page, 'header', examples.default, {
async afterInitialisation($module) {
const { Header } = await import('govuk-frontend')
new Header($module)
}
})
).rejects.toMatchObject({
name: 'InitError',
message:
'Root element (`$module`) already initialised (`govuk-header`)'
})
})

it('throws when $module is not set', async () => {
await expect(
render(page, 'header', examples.default, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class NotificationBanner extends GOVUKFrontendComponent {
* @param {NotificationBannerConfig} [config] - Notification banner config
*/
constructor($module, config = {}) {
super()
super($module)

if (!($module instanceof HTMLElement)) {
throw new ElementError({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint-disable no-new */

const { render } = require('@govuk-frontend/helpers/puppeteer')
const { getExamples } = require('@govuk-frontend/lib/components')

Expand Down Expand Up @@ -236,6 +238,21 @@ describe('Notification banner', () => {
})
})

it('throws when initialised twice', async () => {
await expect(
render(page, 'notification-banner', examples.default, {
async afterInitialisation($module) {
const { NotificationBanner } = await import('govuk-frontend')
new NotificationBanner($module)
}
})
).rejects.toMatchObject({
name: 'InitError',
message:
'Root element (`$module`) already initialised (`govuk-notification-banner`)'
})
})

it('throws when $module is not set', async () => {
await expect(
render(page, 'notification-banner', examples.default, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export class Radios extends GOVUKFrontendComponent {
* @param {Element | null} $module - HTML element to use for radios
*/
constructor($module) {
super()
super($module)

if (!($module instanceof HTMLElement)) {
throw new ElementError({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint-disable no-new */

const {
goToExample,
getProperty,
Expand Down Expand Up @@ -320,6 +322,20 @@ describe('Radios', () => {
})
})

it('throws when initialised twice', async () => {
await expect(
render(page, 'radios', examples.default, {
async afterInitialisation($module) {
const { Radios } = await import('govuk-frontend')
new Radios($module)
}
})
).rejects.toMatchObject({
name: 'InitError',
message: 'Root element (`$module`) already initialised (`govuk-radios`)'
})
})

it('throws when $module is not set', async () => {
await expect(
render(page, 'radios', examples.default, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export class SkipLink extends GOVUKFrontendComponent {
* @throws {ElementError} when the linked element is missing or the wrong type
*/
constructor($module) {
super()
super($module)

if (!($module instanceof HTMLAnchorElement)) {
throw new ElementError({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint-disable no-new */

const { render } = require('@govuk-frontend/helpers/puppeteer')
const { getExamples } = require('@govuk-frontend/lib/components')

Expand Down Expand Up @@ -127,6 +129,21 @@ describe('Skip Link', () => {
})
})

it('throws when initialised twice', async () => {
await expect(
render(page, 'skip-link', examples.default, {
async afterInitialisation($module) {
const { SkipLink } = await import('govuk-frontend')
new SkipLink($module)
}
})
).rejects.toMatchObject({
name: 'InitError',
message:
'Root element (`$module`) already initialised (`govuk-skip-link`)'
})
})

it('throws when $module is not set', async () => {
return expect(
render(page, 'skip-link', examples.default, {
Expand Down
2 changes: 1 addition & 1 deletion packages/govuk-frontend/src/govuk/components/tabs/tabs.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export class Tabs extends GOVUKFrontendComponent {
* @param {Element | null} $module - HTML element to use for tabs
*/
constructor($module) {
super()
super($module)

if (!$module) {
throw new ElementError({
Expand Down
Loading

0 comments on commit 5987b57

Please sign in to comment.