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

Announcer: Part 1 #2362

Open
wants to merge 10 commits into
base: announcer
Choose a base branch
from
Open

Announcer: Part 1 #2362

wants to merge 10 commits into from

Conversation

marcysutton
Copy link
Member

@marcysutton marcysutton commented Nov 15, 2024

The initial implementation for a live region component! I'm getting this draft PR up so I can test with the remote URL.

Jira Issue: https://khanacademy.atlassian.net/browse/WB-1768

Outstanding questions/work areas:

  • Testing in AT to ensure messages are heard when the regions are appended the first time.
  • Debugging the IDREF returned from sendMessage to ensure clearing messages works. This doesn't seem super critical since messages get cleared after 5000ms (and this is configurable), but I want to make sure there aren't any bugs. The Jest test for it passes but the ID returned is empty in Storybook 🤷‍♀️
  • The HMR issue in Storybook. If you save a file while the story is open, it loses all references to elements. I wrote code to fix this, but it could be cool to remove it if I found another way.
  • More integration with React. I did a bit of React integration to use Testing Library in the way they want you to, but the real deal will be experimenting with it in Wonder Blocks and webapp.
  • Refactoring of methods: ensure they have clear names, do fewer things at once, etc.

Copy link

changeset-bot bot commented Nov 15, 2024

🦋 Changeset detected

Latest commit: 2428b60

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@khanacademy/wonder-blocks-announcer Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Contributor

github-actions bot commented Nov 15, 2024

Size Change: +1.55 kB (+1.54%)

Total Size: 102 kB

Filename Size Change
packages/wonder-blocks-announcer/dist/es/index.js 1.55 kB +1.55 kB (new file) 🆕
ℹ️ View Unchanged
Filename Size
packages/wonder-blocks-accordion/dist/es/index.js 3.78 kB
packages/wonder-blocks-banner/dist/es/index.js 1.53 kB
packages/wonder-blocks-birthday-picker/dist/es/index.js 1.77 kB
packages/wonder-blocks-breadcrumbs/dist/es/index.js 887 B
packages/wonder-blocks-button/dist/es/index.js 4.04 kB
packages/wonder-blocks-cell/dist/es/index.js 2.01 kB
packages/wonder-blocks-clickable/dist/es/index.js 3.06 kB
packages/wonder-blocks-core/dist/es/index.js 3.44 kB
packages/wonder-blocks-data/dist/es/index.js 6.24 kB
packages/wonder-blocks-dropdown/dist/es/index.js 18.2 kB
packages/wonder-blocks-form/dist/es/index.js 6.28 kB
packages/wonder-blocks-grid/dist/es/index.js 1.36 kB
packages/wonder-blocks-i18n/dist/es/index.js 4.76 kB
packages/wonder-blocks-icon-button/dist/es/index.js 3 kB
packages/wonder-blocks-icon/dist/es/index.js 871 B
packages/wonder-blocks-labeled-field/dist/es/index.js 72 B
packages/wonder-blocks-layout/dist/es/index.js 1.82 kB
packages/wonder-blocks-link/dist/es/index.js 2.28 kB
packages/wonder-blocks-modal/dist/es/index.js 5.36 kB
packages/wonder-blocks-pill/dist/es/index.js 1.65 kB
packages/wonder-blocks-popover/dist/es/index.js 4.87 kB
packages/wonder-blocks-progress-spinner/dist/es/index.js 1.52 kB
packages/wonder-blocks-search-field/dist/es/index.js 1.3 kB
packages/wonder-blocks-switch/dist/es/index.js 1.94 kB
packages/wonder-blocks-testing-core/dist/es/index.js 3.74 kB
packages/wonder-blocks-testing/dist/es/index.js 1.07 kB
packages/wonder-blocks-theming/dist/es/index.js 693 B
packages/wonder-blocks-timing/dist/es/index.js 1.8 kB
packages/wonder-blocks-tokens/dist/es/index.js 2.36 kB
packages/wonder-blocks-toolbar/dist/es/index.js 827 B
packages/wonder-blocks-tooltip/dist/es/index.js 7.08 kB
packages/wonder-blocks-typography/dist/es/index.js 1.23 kB

compressed-size-action

Copy link
Contributor

github-actions bot commented Nov 15, 2024

A new build was pushed to Chromatic! 🚀

https://5e1bf4b385e3fb0020b7073c-onaptwedxt.chromatic.com/

Chromatic results:

Metric Total
Captured snapshots 370
Tests with visual changes 1
Total stories 504
Inherited (not captured) snapshots [TurboSnap] 0
Tests on the build 370

@marcysutton marcysutton marked this pull request as ready for review November 20, 2024 21:28
@khan-actions-bot
Copy link
Contributor

Gerald

Required Reviewers
  • @Khan/wonder-blocks for changes to .changeset/thirty-ducks-type.md, __docs__/wonder-blocks-announcer/announcer.stories.tsx, packages/wonder-blocks-announcer/CHANGELOG.md, packages/wonder-blocks-announcer/package.json, packages/wonder-blocks-announcer/tsconfig-build.json, static/sb-styles/preview.css, packages/wonder-blocks-announcer/src/announcer.types.ts, packages/wonder-blocks-announcer/src/index.ts, packages/wonder-blocks-announcer/src/__tests__/clear-messages.test.tsx, packages/wonder-blocks-announcer/src/__tests__/send-message.test.tsx, packages/wonder-blocks-announcer/src/components/send-message-button.tsx

Don't want to be involved in this pull request? Comment #removeme and we won't notify you of further changes.

@khan-actions-bot khan-actions-bot requested a review from a team November 20, 2024 21:28
Copy link
Contributor

npm Snapshot: Published

🎉 Good news!! We've packaged up the latest commit from this PR (1053704) and published all packages with changesets to npm.

You can install the packages in webapp by running:

./services/static/dev/tools/deploy_wonder_blocks.js --tag="PR2362"

Packages can also be installed manually by running:

yarn add @khanacademy/wonder-blocks-<package-name>@PR2362

Copy link
Member

@jandrade jandrade left a comment

Choose a reason for hiding this comment

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

This is looking really good! Giving my initial review with some suggestions on how to structure the files to be more consistent with the rest of the repo and asking some questions to get a bit more context on certain parts. This is great progress 👏

Copy link
Member

Choose a reason for hiding this comment

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

suggestion: you can get rid of this file as changesets will create it automatically when the package is released to npm.

Copy link
Member

Choose a reason for hiding this comment

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

suggestion: This seems to be only used for tests. In that case, I'd suggest to move it to the test file directly. This will help us understand better what's part of the API and what is internal tooling.

* @param {number} removalDelay Optional duration to remove a message after sending. Defaults to 5000ms.
* @returns {string} IDREF for targeted live region element
*/
export function sendMessage({
Copy link
Member

Choose a reason for hiding this comment

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

suggestion: I understand that this is the first iteration. I'd suggest moving this into a separate file so the code is more isolated (and tests can be isolated as you do it already).

I'd suggest doing something similar with clearMessages.

You can see how other packages follow this structure (for example, wb-core).

Copy link
Member Author

Choose a reason for hiding this comment

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

One consideration for this is I was using a shared variable for the announcer in both of those API methods. I will lose that variable access when moving to separate files. One solution would be to use a global on the window object. Do you have any objections or suggestions for how to work around that?

Comment on lines +33 to +40
if (typeof jest === "undefined") {
setTimeout(() => {
return announcer?.announce(message, level, removalDelay);
}, 100);
} else {
// If we are in a test environment, announce without waiting
return announcer.announce(message, level, removalDelay);
}
Copy link
Member

Choose a reason for hiding this comment

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

suggestion: I'd strongly recommend not using test checks in the prod code. Instead, you could probably use advanceTimersByTime in during test setup.

e.g.

// Act
do something to trigger the announcer
jest.advanceTimersByTime(100);

For reference: https://jestjs.io/docs/timer-mocks#advance-timers-by-time

You can see some existing examples in the codebase.

Comment on lines +34 to +36
setTimeout(() => {
return announcer?.announce(message, level, removalDelay);
}, 100);
Copy link
Member

Choose a reason for hiding this comment

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

question: Is there a specific reason why we need to use this timeout? I wonder if there's another way to handle this case, but I don't think I have full context on that.

Copy link
Member Author

Choose a reason for hiding this comment

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

It was for Safari and VoiceOver, but I can play around with it again to see if we really need it.


private constructor() {
if (typeof document !== "undefined") {
const topLevelId = `wbAnnounce`;
Copy link
Member

Choose a reason for hiding this comment

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

suggestion: This is a good candidate for a constant at the file level so this ID can be shared across the file. wdyt?

Copy link
Member Author

Choose a reason for hiding this comment

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

That's what I had at first, but it added cognitive load when working with the API. It makes the file a lot more scannable to locate this ID closer to the code it's used on!

Object.assign(this.node.style, srOnly);

const aWrapper = this.createRegionWrapper("assertive");
this.createDuplicateRegions(aWrapper, "assertive");
Copy link
Member

Choose a reason for hiding this comment

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

question: Could you please elaborate why we need to create duplicate regions here? I was looking at the story in the browser and I'm not sure what should expect from that.

Copy link
Member Author

Choose a reason for hiding this comment

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

It's an architectural decision to try and prevent messages from being dropped by AT, something I picked up back in the Angular.js days. Coupled with the debounce logic I'm working on, I think I'll keep it in for now to ensure unique messages are appended and always picked up by AT.

/**
* Styling for live region.
* TODO: move to wonder-blocks-style package.
* Note: This style is overridden in Storybook for testing.
Copy link
Member

Choose a reason for hiding this comment

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

praise: Thanks for adding this note 🫶

/**
* Internal class to manage screen reader announcements.
*/
class Announcer {
Copy link
Member

Choose a reason for hiding this comment

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

suggestion: I'd recommend moving this class into a separate file as well. Sometimes we use an internal/ folder for things like this, but up to you to decide what folder is best.

Side question: Do you think that Announcer can be tested independently? or is it better to rely on the exposed APIs for unit testing (the ones that you already created)?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good question about testing! It seems redundant to test the class itself, at least the parts that affect the DOM. But perhaps some unit testing for the singleton and some of the utility methods would be a good idea?

Copy link
Member

Choose a reason for hiding this comment

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

suggestion: Related to some of the comments left in this file. For consistency, we recommend splitting this file into separate files and keep this index.ts file as a barrel file. To clarify, I'm 100% with you about avoiding barrel files, but in the WB case, it really helps to make more clear and explicit what's going to be public API and what's internal/private. You can check other packages to see how we usually use this approach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants