Repository: storybook
Issue: [Bug]: a11y-addon - APCA Contrast check - Can't pass custom evaluate function to rule
Labels: bug,addon: a11y,sev:S3

### Describe the bug

I am trying to integrate Stack Exchange's implementation of the [APCA contrast check](https://github.com/StackExchange/apca-check) by extending the `a11y` addon options. For now I've duplicated his script into a separate folder in the project and modified his function to not run `axe.configure` but instead return the `check` and `rule` manually. 

<img width="744" alt="Screen Shot 2024-06-19 at 3 29 30 PM" src="https://github.com/storybookjs/storybook/assets/15971341/c9fc37a6-1ed1-4972-8c9c-6c966a839610">

The `axe` docs are a little wonky, and say that `evaluate` accepts a string, but if you look into the code, the type does accept string or function. 

I've got it working and showing up on Storybook in the `a11y` addon, but the `check` always comes back as 'Incomplete.' Are we passing the `node` differently somehow? I've tried accessing the `virtualNode.actualNode` as well with no luck. Just always returns 'Incomplete'. I can't even force it to return `true` or `false` as well. The APCA contrast check works on it's own with a manual `axe` setup, but not inside of Storybook. 

The updated function is below. Am I missing anything? 

In `preview.ts`
```js
import type { Preview } from "@storybook/react";
import '../src/assets/styles/globals.scss'
import '../public/main.latin.min.css'
import { registerAPCACheck } from '../vendor/apca-check/index.js';

const preview: Preview = {
  parameters: {
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/i,
      },
    },
    backgrounds: {
      default: 'light',
      values: [
        { name: 'light', value: 'rgba(255,255,255,1.00)' },
        { name: 'dark', value: 'rgba(33,47,61,1.00)' },
      ],
    },
    a11y: {
      config: {
        checks: [
          { ... registerAPCACheck('silver-plus').check },
        ],
        rules: [
          { ...registerAPCACheck('silver-plus').rule },
          { id: "color-contrast", enabled: false },
        ],
      },
    }
  },
};


export default preview;
```

In `apca-check.js`, just a copy + pase from the npm package. For `apca-bronze.js` / `apca-silver-plus.js`, they are copy and pastes as well.

```js
const apcaW3 = require("apca-w3");
const calcAPCA = apcaW3.calcAPCA;
const APCABronzeConformanceThresholdFn = require("./apca-bronze.js");
const APCASilverPlusConformanceThresholdFn = require("./apca-silver-plus.js");
const axe = require("axe-core");

const generateColorContrastAPCAConformanceCheck = (
  conformanceLevel,
  conformanceThresholdFn
) => {
  return {
    id: `color-contrast-apca-${conformanceLevel}-conformance`,
    metadata: {
      impact: "serious",
      messages: {
        pass:
          "Element has sufficient APCA " +
          conformanceLevel +
          "level lightness contrast (Lc) of ${this.data.apcaContrast}Lc (foreground color: ${this.data.fgColor}, background color: ${this.data.bgColor}, font size: ${this.data.fontSize}, font weight: ${this.data.fontWeight}). Expected minimum APCA contrast of ${this.data.apcaThreshold}}",
        fail: {
          default:
            "Element has insufficient APCA " +
            conformanceLevel +
            "level contrast of ${this.data.apcaContrast}Lc (foreground color: ${this.data.fgColor}, background color: ${this.data.bgColor}, font size: ${this.data.fontSize}, font weight: ${this.data.fontWeight}). Expected minimum APCA lightness contrast of ${this.data.apcaThreshold}Lc",
          increaseFont:
            "Element has insufficient APCA " +
            conformanceLevel +
            " level contrast of ${this.data.apcaContrast}Lc (foreground color: ${this.data.fgColor}, background color: ${this.data.bgColor}, font size: ${this.data.fontSize}, font weight: ${this.data.fontWeight}). Increase font size and/or font weight to meet APCA conformance minimums",
        },
        incomplete: "Unable to determine APCA lightness contrast (Lc)",
      },
    },
    evaluate(node) {      
      const nodeStyle = window.getComputedStyle(node);
      const fontSize = nodeStyle.getPropertyValue("font-size");
      const fontWeight = nodeStyle.getPropertyValue("font-weight");
      const bgColor = axe.commons.color.getBackgroundColor(node);
      const fgColor = axe.commons.color.getForegroundColor(
        node,
        false,
        bgColor
      );
      // missing data to determine APCA contrast for this node
      if (!bgColor || !fgColor || !fontSize || !fontWeight) {
        return undefined;
      }
      const toRGBA = (color) => {
        return `rgba(${color.red}, ${color.green}, ${color.blue}, ${color.alpha})`;
      };
      const apcaContrast = Math.abs(calcAPCA(toRGBA(fgColor), toRGBA(bgColor)));
      const apcaThreshold = conformanceThresholdFn(fontSize, fontWeight);
      this.data = {
        fgColor: fgColor.toHexString(),
        bgColor: bgColor.toHexString(),
        fontSize: `${((parseFloat(fontSize) * 72) / 96).toFixed(1)}pt (${parseFloat(fontSize)}px)`,
        fontWeight: fontWeight,
        apcaContrast: Math.round(apcaContrast * 100) / 100,
        apcaThreshold: apcaThreshold,
        messageKey: apcaThreshold === null ? "increaseFont" : "default",
      };

      return apcaThreshold ? apcaContrast >= apcaThreshold : void 0;
    },
  };
};

const generateColorContrastAPCARule = (conformanceLevel) => ({
  id: `color-contrast-apca-${conformanceLevel}`,
  impact: "serious",
  matches: "color-contrast-matches",
  metadata: {
    help: "Elements must meet APCA conformance minimums thresholds",
    description: `Ensures the contrast between foreground and background colors meets APCA ${conformanceLevel} level conformance minimums thresholds`,
    helpUrl:
      "https://readtech.org/ARC/tests/visual-readability-contrast/?tn=criterion",
  },
  all: [`color-contrast-apca-${conformanceLevel}-conformance`],
  tags: ["apca", "wcag3", `apca-${conformanceLevel}`],
});

const registerAPCACheck = (conformanceLevel, customConformanceThresholdFn) => {
  if (
    conformanceLevel === "custom" &&
    typeof customConformanceThresholdFn !== "function"
  ) {
    throw new Error(
      "A custom conformance level requires a custom conformance threshold function"
    );
  }
  const conformanceThresholdFnMap = {
    bronze: APCABronzeConformanceThresholdFn,
    silver: APCASilverPlusConformanceThresholdFn,
    custom: customConformanceThresholdFn,
  };
  return {
    rule: generateColorContrastAPCARule(conformanceLevel),
    check: generateColorContrastAPCAConformanceCheck(
      conformanceLevel,
      conformanceThresholdFnMap[conformanceLevel]
    ),
  };
};

module.exports = { registerAPCACheck };
```



### Reproduction link

https://github.com/StackExchange/apca-check

### Reproduction steps

_No response_

### System

```bash
Storybook Environment Info:
(node:81977) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)

  System:
    OS: macOS 12.0.1
    CPU: (8) x64 Apple M1
    Shell: 5.8 - /bin/zsh
  Binaries:
    Node: 21.0.0 - ~/.nvm/versions/node/v21.0.0/bin/node
    Yarn: 1.22.22 - ~/.nvm/versions/node/v21.0.0/bin/yarn
    npm: 10.2.0 - ~/.nvm/versions/node/v21.0.0/bin/npm <----- active
    pnpm: 9.4.0 - ~/.nvm/versions/node/v21.0.0/bin/pnpm
  Browsers:
    Chrome: 126.0.6478.62
    Safari: 15.1
  npmPackages:
    @storybook/addon-a11y: ^8.1.10 => 8.1.10
    @storybook/addon-backgrounds: ^8.1.9 => 8.1.9
    @storybook/addon-console: ^3.0.0 => 3.0.0
    @storybook/addon-essentials: ^8.1.9 => 8.1.9
    @storybook/addon-interactions: ^8.1.9 => 8.1.9
    @storybook/addon-links: ^8.1.9 => 8.1.9
    @storybook/addon-onboarding: ^8.1.9 => 8.1.9
    @storybook/addon-styling-webpack: ^1.0.0 => 1.0.0
    @storybook/blocks: ^8.1.9 => 8.1.9
    @storybook/nextjs: ^8.1.9 => 8.1.9
    @storybook/react: ^8.1.9 => 8.1.9
    @storybook/test: ^8.1.9 => 8.1.9
    eslint-plugin-storybook: ^0.8.0 => 0.8.0
    storybook: ^8.1.9 => 8.1.9
```


### Additional context

_No response_