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

Add MissingElementError and use it within the Skip Link #4177

Merged
merged 4 commits into from
Sep 7, 2023

Conversation

domoscargin
Copy link
Contributor

Closes #4128

Throws an error instead of returning if the linked element is missing.

I considered implementing a separate method to check for this, but that feels a bit needless for Skip Link - it may be necessary for more complex components, though.

There's also an argument for throwing errors within getLinkedElement and getFragmentFromUrl (our error message could potentially be more helpful if we could provide the linkedElementId, for example), but since these get caught by the existing check, I kept things simple.

@domoscargin domoscargin added this to the v5.0 milestone Sep 5, 2023
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-4177 September 5, 2023 13:47 Inactive
@github-actions
Copy link

github-actions bot commented Sep 5, 2023

📋 Stats

File sizes

File Size
dist/govuk-frontend-4.6.0.min.css 118.07 KiB
dist/govuk-frontend-4.6.0.min.js 47.1 KiB
dist/govuk-frontend-ie8-4.6.0.min.css 79.27 KiB
packages/govuk-frontend/dist/govuk/all.bundle.js 72.92 KiB
packages/govuk-frontend/dist/govuk/all.bundle.mjs 68.5 KiB
packages/govuk-frontend/dist/govuk/all.mjs 4 KiB
packages/govuk-frontend/dist/govuk/govuk-frontend-component.mjs 359 B
packages/govuk-frontend/dist/govuk/govuk-frontend.min.js 35.39 KiB
packages/govuk-frontend/dist/govuk/i18n.mjs 5.83 KiB

Modules

File Size
all.mjs 65.04 KiB
components/accordion/accordion.mjs 20.86 KiB
components/button/button.mjs 3.67 KiB
components/character-count/character-count.mjs 20.46 KiB
components/checkboxes/checkboxes.mjs 4.37 KiB
components/error-summary/error-summary.mjs 4.96 KiB
components/exit-this-page/exit-this-page.mjs 15.42 KiB
components/header/header.mjs 2.3 KiB
components/notification-banner/notification-banner.mjs 3.5 KiB
components/radios/radios.mjs 3.37 KiB
components/skip-link/skip-link.mjs 2.67 KiB
components/tabs/tabs.mjs 8.04 KiB

View stats and visualisations on the review app


Action run for e1eff14

@colinrotherham
Copy link
Contributor

There's also an argument for throwing errors within getLinkedElement and getFragmentFromUrl (our error message could potentially be more helpful if we could provide the linkedElementId, for example), but since these get caught by the existing check, I kept things simple.

Not a blocker in the slightest, but knowing what's missing is kinda useful!

Could throw (and catch) an error in this.getLinkedElement() but add to { cause } for the developer tools?

try {
  this.$linkedElement = this.getLinkedElement()
} catch (cause) {
  throw new MissingElementError('The linked HTML element does not exist', {
    cause
  })
}

Would be helpful to see what linkedElementId was for sure

Copy link
Contributor

@colinrotherham colinrotherham left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved! Added a comment about an error cause

For any error thrown it's good to explain what caused it

@domoscargin
Copy link
Contributor Author

@colinrotherham Added a commit that addresses your comment. As discussed, I've had to check the type of cause.

@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-4177 September 7, 2023 12:18 Inactive
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-4177 September 7, 2023 12:19 Inactive
Comment on lines +28 to +30
export class MissingElementError extends GOVUKFrontendError {
name = 'MissingElementError'
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a blocker, but it's triggered a discussion

To keep file size down, could we use Error { cause } to keep our custom errors to a minimum? Otherwise our published package will grow as custom errors are added

E.g. Using TypeError "…not of the expected type" for null or missing things

Component errors

Here's an example with nested { cause } to make the error more informative:

throw new ComponentError('I has died', {
  cause: new TypeError("Argh, 'ere be no link target #pirates"),
  component: 'Skip link'
}

But using the nested { cause } approach to surface all that useful information. It's handy for monitoring tools like Sentry which links chained errors + stack traces

For extra user friendliness a custom toString() (see below) could log the component name:

ComponentError: "Skip link: I has died"
 → Cause: TypeError: "Argh, 'ere be no link target #pirates"

Suggested change
export class MissingElementError extends GOVUKFrontendError {
name = 'MissingElementError'
}
export class ComponentError extends GOVUKFrontendError {
name = 'ComponentError'
component = 'Component name'
toString() {
return `${this.component}: ${this.message}`;
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's also a gotcha

We'd need to confirm Babel transforms or a polyfill for JavaScript built-in: Error: cause

@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-4177 September 7, 2023 12:39 Inactive
throw new MissingElementError(
'Skip link: the linked HTML element does not exist',
{
cause: cause instanceof Error ? cause : undefined
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For JavaScript built-in: Error: cause browser support are we happy treating this as optional?

If so, do you mind adding a bit of safety to the GOVUKFrontendError constructor?

i.e. Use feature detection, not a polyfill

export class GOVUKFrontendError extends Error {
  name = 'GOVUKFrontendError'

  /**
   * @param {string} message 
   * @param {ErrorOptions} options
   */
  constructor(message, { cause, ...options }) {
    super(message, options)

    // Add cause property where supported
    if (cause && new Error('test', { cause }).cause === cause) {
      this.cause = cause
    }
  }
}

We'll need to:

  1. ESLint ignore "ES2022 Error Cause is forbidden" for feature detection purposes
  2. ESLint ignore "ES2018 rest/spread properties are forbidden" if Babel handles it already
  3. Triple check my feature detection idea, maybe it needs a try/catch?

For 3) it's mainly because 'cause' in Error.prototype didn't work 🙈

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or we remove { cause } for another day?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm leaning toward leaving { cause } for now - feels like p'raps we need a bit more thinking about how we make it work and can do that as part of iterating on these errors.

I've pushed a change to use the error messages to convey the error to the user, whaddya reckon?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very clever, absolutely love it

@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-4177 September 7, 2023 13:50 Inactive
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-4177 September 7, 2023 13:59 Inactive
Copy link
Contributor

@colinrotherham colinrotherham left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Brilliant @domoscargin

Some non-blocking things since the last push but approved! Thanks again for making the errors actionable, always good to explain why an error was throw if we can

Appreciate we've used double quotes here but back ticks in #4104 so I've written up some content feedback for later:

Error causes

We had some good chats about error causes, especially with monitoring tools like Sentry linking chained errors + stack traces together for inspection

So we should pull out some actions/discussions such as:

  1. Using Error cause to reduce custom errors (see comment)
  2. Spike Error cause feature detection (see comment)

const linkedElement = document.getElementById(linkedElementId)

if (!linkedElement) {
throw new TypeError(`Target selector "#${linkedElementId}" not found`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a blocker, but we now say "linked element" in the fallback error but "target selector" here

Suggested change
throw new TypeError(`Target selector "#${linkedElementId}" not found`)
throw new TypeError(`Linked element selector "#${linkedElementId}" not found`)

try {
const $linkedElement = this.getLinkedElement()
this.$linkedElement = $linkedElement
} catch (err) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another non-blocker, but we use error everywhere else

Suggested change
} catch (err) {
} catch (error) {

@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-4177 September 7, 2023 14:23 Inactive
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-4177 September 7, 2023 14:29 Inactive
@domoscargin domoscargin merged commit 1d1fb0a into main Sep 7, 2023
44 checks passed
@domoscargin domoscargin deleted the bk-skip-link-error branch September 7, 2023 14:41
@romaricpascal romaricpascal mentioned this pull request Dec 8, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Throw errors during Skip Link initialisation if key HTML elements are missing
3 participants