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

refactor: make element spec processing more cosistent #4093

Merged
merged 23 commits into from
Aug 21, 2023

Conversation

dbjorge
Copy link
Contributor

@dbjorge dbjorge commented Jul 17, 2023

This PR implements hooks for customizing how DqElements are serialized.

Opening this as a draft for @WilcoFiers to pick up since I wasn't able to get it finished before heading off. The implementation is complete and it's passing integration tests (both old and new), but it still needs unit test updates (both to test the changes and also to fix up existing tests that relied on result nodes sometimes being DqElements).

After this PR, the only usage of DqElement within axe-core itself is always of form new DqElement(node, options).toJSON(), ie, serialized immediately after construction. I think this suggests that it'd be a better design to drop the class entirely and just have a serializeVirtualNode function instead that isn't unnecessarily stateful, but that's likely too breaking. We might consider marking DqElement as deprecated, though, to try to mitigate folks getting confused and thinking that they should be using it.

Closes: #3471

@@ -203,7 +203,7 @@ describe('Configure Options', function () {
};

var iframe = document.createElement('iframe');
iframe.src = '/test/mock/frames/context.html';
iframe.src = '/test/mock/frames/noHtml-config.html';
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The original test was configureing in the top frame but not the child frame. This isn't supported and doesn't provide the protection noHtml is intended to (the thing that the test after this one is verifying). However, previously, the original test worked despite testing an unsupported configuration because there was an extra round trip through DqElement in the top frame involved that no longer exists.

I think it's preferable to break the old pattern and demand that configure must be called consistently in each frame, so I changed the test rather than forcing a top-frame-only noHtml configure to suppress html properties from child frame results, but if that feels too concerning for backcompat reasons you could address it by adding an audit.noHtml test into the process-aggregate.js code that's filling in html from node.source.

@hamax
Copy link
Contributor

hamax commented Jul 18, 2023

Should the default serializer take into account options like options.selectors? I wonder if this could have bad performance implications. Otherwise I like that we can add custom element data to results with this. One problem I see is that custom properties wouldn't be copied over in the reporter processAggregate, so if we use this we would need to implement a custom reporter (probably having to re-implement everything in processAggregate).

@WilcoFiers WilcoFiers changed the title feat: add hook for DqElement serializer refactor: make element spec processing more cosistent Jul 25, 2023
@WilcoFiers WilcoFiers marked this pull request as ready for review July 25, 2023 15:04
@WilcoFiers WilcoFiers requested a review from a team as a code owner July 25, 2023 15:04
@WilcoFiers WilcoFiers force-pushed the dbjorge/node-serializer branch from 811d4aa to 9c48f1c Compare July 26, 2023 10:11
test/core/public/run-partial.js Outdated Show resolved Hide resolved
Comment on lines +194 to +195
assert.isNull(passes[0].nodes[0].any[0].relatedNodes[0].html);
assert.isNull(violations[0].nodes[0].any[0].relatedNodes[0].html);
Copy link
Contributor

Choose a reason for hiding this comment

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

Prior to this, node.html would be null, but relatedNode.html was "Undefined". That was a bug.

Copy link
Contributor

@straker straker left a comment

Choose a reason for hiding this comment

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

We probably need to add the setRunOptions to run virtual rule as well.

lib/core/public/run-partial.js Show resolved Hide resolved
lib/core/utils/node-serializer.js Outdated Show resolved Hide resolved
serializer = newSerializer;
}

Object.assign(nodeSerializer, {
Copy link
Contributor

Choose a reason for hiding this comment

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

Adding functions onto another function hides those functions from the user. They won't get code completion nor typescript support for this. Is there a reason you didn't want to have nodeSerializer be an object with a setSerializer function?

Copy link
Contributor

Choose a reason for hiding this comment

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

Why wouldn't you be able to type / get intellisense on this? I think this would do it:

  type NodeToSpec = <T extends SerialDqElement = SerialDqElement>
    (node: VirtualNode | Element) => T;
  type MergeSpecs = <T extends SerialDqElement = SerialDqElement>
    (parentSpec: SerialDqElement, childSpec: SerialDqElement) => T;

  interface Utils {
    nodeSerializer: {
      ({ isSpec, mergeSpecs }: { isSpec?: NodeToSpec, mergeSpecs?: MergeSpecs }): void,
      isSpec: NodeToSpec,
      mergeSpecs: NodeToSpec
    }

But sure, this could also just be an object with a "set" function if you want. I like this approach better personally, but it's not a big deal.

Copy link
Contributor

Choose a reason for hiding this comment

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

As discussed, I'm replacing this with a .update() method.

test/core/base/audit.js Outdated Show resolved Hide resolved
lib/core/utils/node-serializer.js Show resolved Hide resolved
@WilcoFiers
Copy link
Contributor

WilcoFiers commented Aug 8, 2023

We probably need to add the setRunOptions to run virtual rule as well.

This isn't useful, as the only time DqElement will be part of runVirtualRule is if that vNode includes an actualNode. But then if you do that, this throws becauseaxe._selectorData isn't set up correctly. Adding this won't do anything, and there's no way to test it either because it throws before these options are applied.

const fixture = document.querySelector('#fixture')
fixture.innerHTML = '<i role="invalid-role"></i>'
const vNode = axe.setup(fixture.firstElementChild);
const result = axe.runVirtualRule('aria-roles', vNode);
// TypeError: Cannot read properties of undefined (reading 'role="invalid-role"')

If there's ever a use case for it, solving for it belongs in a different PR.

lib/core/utils/check-helper.js Outdated Show resolved Hide resolved
lib/core/reporters/helpers/process-aggregate.js Outdated Show resolved Hide resolved
@WilcoFiers WilcoFiers force-pushed the dbjorge/node-serializer branch from cebf827 to d1b07e1 Compare August 11, 2023 10:11
@dbjorge
Copy link
Contributor Author

dbjorge commented Aug 11, 2023

@WilcoFiers I just have the two most recent unresolved comments remaining (it's technically my PR so I can't "request changes")

@WilcoFiers WilcoFiers force-pushed the dbjorge/node-serializer branch from e546e14 to c59ad63 Compare August 15, 2023 16:05
lib/core/utils/check-helper.js Outdated Show resolved Hide resolved
Co-authored-by: Steven Lambert <2433219+straker@users.noreply.github.com>
@dbjorge
Copy link
Contributor Author

dbjorge commented Aug 18, 2023

LGTM :shipit: (still technically my PR, so @straker will need to approve)

lib/core/utils/node-serializer.js Outdated Show resolved Hide resolved
* suitable for JSON.stringify to consume. Output must include all properties
* that DqElement.toJSON() would have. Will always be invoked from the
* input element's original page context.
* @property {Function} mergeSpecs (Optional) Merges two specs (produced by toSpec) which
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this also include a note about needing to pass through all properties that DqElement.mergeSpec would pass through (ancestry, selector, etc.) just like the note for toSpec?

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think that's crucial, but if you have a suggestion I'll put it in 😁

Co-authored-by: Steven Lambert <2433219+straker@users.noreply.github.com>
@WilcoFiers WilcoFiers merged commit 381b2c3 into develop Aug 21, 2023
@WilcoFiers WilcoFiers deleted the dbjorge/node-serializer branch August 21, 2023 16:18
hamax added a commit to hamax/axe-core that referenced this pull request Aug 1, 2024
This API was added in pr dequelabs#4093, but TS definitions were never added.
hamax added a commit to hamax/axe-core that referenced this pull request Aug 14, 2024
This API was added in pr dequelabs#4093, but TS definitions were never added.
dbjorge pushed a commit that referenced this pull request Sep 24, 2024
This API was added in pr #4093, but TS definitions were never added.

For simplicity I'm using SerialDqElement in the API. We could introduce
a generic for the custom serialized type (T extends SerialDqElement),
but it's hard to consistently use it everywhere (AxeReporter,
NodeSerializer.dqElmToSpec).

I also fixed DqElement.mergeSpecs which is needed to implement a custom
node serializer: it exists on the constructor and not on the individual
instances
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.

Create utils.nodeSerializer method
4 participants