Skip to content

Commit

Permalink
Remove GOVUKFrontend prefix from Component
Browse files Browse the repository at this point in the history
- `GOVUKFrontendComponent` is now `Component`. Renamed
`govuk-frontend-component.mjs` to `component.mjs` and the associated
test. Changed references to `GOVUKFrontendComponent` to `Component` in
the test.
- Created new `govuk-frontend-component.mjs` that imports `Component`
and exports it as `GOVUKFrontendComponent` to avoid a breaking change.
  • Loading branch information
patrickpatrickpatrick committed Nov 20, 2024
1 parent 3c8fec3 commit 4dbcb4a
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 121 deletions.
3 changes: 2 additions & 1 deletion packages/govuk-frontend/src/govuk/all.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ export { SkipLink } from './components/skip-link/skip-link.mjs'
export { Tabs } from './components/tabs/tabs.mjs'
export { initAll, createAll } from './init.mjs'
export { isSupported } from './common/index.mjs'
export { GOVUKFrontendComponent as Component } from './govuk-frontend-component.mjs'
export { Component } from './component.mjs'
export { GOVUKFrontendComponent } from './govuk-frontend-component.mjs'

/**
* @typedef {import('./init.mjs').Config} Config
Expand Down
1 change: 1 addition & 0 deletions packages/govuk-frontend/src/govuk/all.puppeteer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ describe('GOV.UK Frontend', () => {
'Component',
'ErrorSummary',
'ExitThisPage',
'GOVUKFrontendComponent',
'Header',
'NotificationBanner',
'PasswordInput',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Component } from './component.mjs'
import { SupportError } from './errors/index.mjs'
import { GOVUKFrontendComponent } from './govuk-frontend-component.mjs'

describe('GOVUKFrontendComponent', () => {
describe('Component', () => {
describe('checkSupport()', () => {
beforeEach(() => {
// Jest does not tidy the JSDOM document between tests
Expand All @@ -10,7 +10,7 @@ describe('GOVUKFrontendComponent', () => {
})

describe('default implementation', () => {
class ServiceComponent extends GOVUKFrontendComponent {
class ServiceComponent extends Component {
static moduleName = 'app-service-component'
}

Expand All @@ -27,7 +27,7 @@ describe('GOVUKFrontendComponent', () => {

describe('when overriden', () => {
it('Allows child classes to define their own condition for support', () => {
class ServiceComponent extends GOVUKFrontendComponent {
class ServiceComponent extends Component {
static moduleName = 'app-service-component'

static checkSupport() {
Expand Down
113 changes: 113 additions & 0 deletions packages/govuk-frontend/src/govuk/component.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { isInitialised, isSupported } from './common/index.mjs'
import { ElementError, InitError, SupportError } from './errors/index.mjs'

/**
* Base Component class
*
* Centralises the behaviours shared by our components
*
* @virtual
* @template {Element} [RootElementType=HTMLElement]
*/
export class Component {
/**
* @type {typeof Element}
*/
static elementType = HTMLElement

// allows Typescript user to work around the lack of types
// in GOVUKFrontend package, Typescript is not aware of $root
// in components that extend GOVUKFrontendComponent
/**
* Returns the root element of the component
*
* @protected
* @returns {RootElementType} - the root element of component
*/
get $root() {
return this._$root
}

/**
* @protected
* @type {RootElementType}
*/
_$root

/**
* Constructs a new component, validating that GOV.UK Frontend is supported
*
* @internal
* @param {Element | null} [$root] - HTML element to use for component
*/
constructor($root) {
const childConstructor = /** @type {ChildClassConstructor} */ (
this.constructor
)

// TypeScript does not enforce that inheriting classes will define a `moduleName`
// (even if we add a `@virtual` `static moduleName` property to this class).
// While we trust users to do this correctly, we do a little check to provide them
// a helpful error message.
//
// After this, we'll be sure that `childConstructor` has a `moduleName`
// as expected of the `ChildClassConstructor` we've cast `this.constructor` to.
if (typeof childConstructor.moduleName !== 'string') {
throw new InitError(`\`moduleName\` not defined in component`)
}

if (!($root instanceof childConstructor.elementType)) {
throw new ElementError({
element: $root,
component: childConstructor,
identifier: 'Root element (`$root`)',
expectedType: childConstructor.elementType.name
})
} else {
this._$root = /** @type {RootElementType} */ ($root)
}

childConstructor.checkSupport()

this.checkInitialised()

const moduleName = childConstructor.moduleName

this.$root.setAttribute(`data-${moduleName}-init`, '')
}

/**
* Validates whether component is already initialised
*
* @private
* @throws {InitError} when component is already initialised
*/
checkInitialised() {
const constructor = /** @type {ChildClassConstructor} */ (this.constructor)
const moduleName = constructor.moduleName

if (moduleName && isInitialised(this.$root, moduleName)) {
throw new InitError(constructor)
}
}

/**
* Validates whether components are supported
*
* @throws {SupportError} when the components are not supported
*/
static checkSupport() {
if (!isSupported()) {
throw new SupportError()
}
}
}

/**
* @typedef ChildClass
* @property {string} moduleName - The module name that'll be looked for in the DOM when initialising the component
*/

/**
* @typedef {typeof Component & ChildClass} ChildClassConstructor
*/
114 changes: 1 addition & 113 deletions packages/govuk-frontend/src/govuk/govuk-frontend-component.mjs
Original file line number Diff line number Diff line change
@@ -1,113 +1 @@
import { isInitialised, isSupported } from './common/index.mjs'
import { ElementError, InitError, SupportError } from './errors/index.mjs'

/**
* Base Component class
*
* Centralises the behaviours shared by our components
*
* @virtual
* @template {Element} [RootElementType=HTMLElement]
*/
export class GOVUKFrontendComponent {
/**
* @type {typeof Element}
*/
static elementType = HTMLElement

// allows Typescript user to work around the lack of types
// in GOVUKFrontend package, Typescript is not aware of $root
// in components that extend GOVUKFrontendComponent
/**
* Returns the root element of the component
*
* @protected
* @returns {RootElementType} - the root element of component
*/
get $root() {
return this._$root
}

/**
* @protected
* @type {RootElementType}
*/
_$root

/**
* Constructs a new component, validating that GOV.UK Frontend is supported
*
* @internal
* @param {Element | null} [$root] - HTML element to use for component
*/
constructor($root) {
const childConstructor = /** @type {ChildClassConstructor} */ (
this.constructor
)

// TypeScript does not enforce that inheriting classes will define a `moduleName`
// (even if we add a `@virtual` `static moduleName` property to this class).
// While we trust users to do this correctly, we do a little check to provide them
// a helpful error message.
//
// After this, we'll be sure that `childConstructor` has a `moduleName`
// as expected of the `ChildClassConstructor` we've cast `this.constructor` to.
if (typeof childConstructor.moduleName !== 'string') {
throw new InitError(`\`moduleName\` not defined in component`)
}

if (!($root instanceof childConstructor.elementType)) {
throw new ElementError({
element: $root,
component: childConstructor,
identifier: 'Root element (`$root`)',
expectedType: childConstructor.elementType.name
})
} else {
this._$root = /** @type {RootElementType} */ ($root)
}

childConstructor.checkSupport()

this.checkInitialised()

const moduleName = childConstructor.moduleName

this.$root.setAttribute(`data-${moduleName}-init`, '')
}

/**
* Validates whether component is already initialised
*
* @private
* @throws {InitError} when component is already initialised
*/
checkInitialised() {
const constructor = /** @type {ChildClassConstructor} */ (this.constructor)
const moduleName = constructor.moduleName

if (moduleName && isInitialised(this.$root, moduleName)) {
throw new InitError(constructor)
}
}

/**
* Validates whether components are supported
*
* @throws {SupportError} when the components are not supported
*/
static checkSupport() {
if (!isSupported()) {
throw new SupportError()
}
}
}

/**
* @typedef ChildClass
* @property {string} moduleName - The module name that'll be looked for in the DOM when initialising the component
*/

/**
* @typedef {typeof GOVUKFrontendComponent & ChildClass} ChildClassConstructor
*/
export { Component as GOVUKFrontendComponent } from './component.mjs'
6 changes: 3 additions & 3 deletions packages/govuk-frontend/src/govuk/init.jsdom.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
} from '@govuk-frontend/lib/names'

import * as GOVUKFrontend from './all.mjs'
import { GOVUKFrontendComponent } from './govuk-frontend-component.mjs'
import { Component } from './component.mjs'
import { initAll, createAll } from './init.mjs'

// Annoyingly these don't get hoisted if done in a loop
Expand Down Expand Up @@ -227,14 +227,14 @@ describe('createAll', () => {
document.body.outerHTML = '<body></body>'
})

class MockComponent extends GOVUKFrontendComponent {
class MockComponent extends Component {
constructor(...args) {
super(...args)
this.args = args
}

static checkSupport() {
GOVUKFrontendComponent.checkSupport()
Component.checkSupport()
}

static moduleName = 'mock-component'
Expand Down

0 comments on commit 4dbcb4a

Please sign in to comment.