Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent multiple initialisations of a single component instance #5272

Merged
merged 3 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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