diff --git a/internal/hierarchy/selector_keys_test.go b/internal/hierarchy/selector_keys_test.go index c0908ef..24f6acd 100644 --- a/internal/hierarchy/selector_keys_test.go +++ b/internal/hierarchy/selector_keys_test.go @@ -8,26 +8,33 @@ import ( "testing" ) +// selectorKeysGolden is the cross-runtime contract for object selectors: the +// keys both runtimes accept, and the diagnostic both raise for a key neither +// can match. +type selectorKeysGolden struct { + Keys []string `json:"keys"` + UnknownKeyExample []string `json:"unknownKeyExample"` + UnknownKeyMessage string `json:"unknownKeyMessage"` +} + // Both runtimes reject an object-selector key they do not know, so the two key // lists have to be one list. Were they to drift, a spec would be accepted by // the runtime that lists the key and fail the run on the one that does not, and // the difference would only show on the platform nobody ran first. // pkg/spec/test/selector-keys.test.ts asserts the SAME file from the web side. func TestSelectorKeysMatchTheCrossRuntimeList(t *testing.T) { - path, err := filepath.Abs("../../pkg/spec/test/fixtures/selector-keys.json") - if err != nil { - t.Fatal(err) + golden := loadSelectorKeysGolden(t) + if got := SelectorKeys(); !slices.Equal(got, golden.Keys) { + t.Errorf("native key list\n got=%v\nwant=%v", got, golden.Keys) } - body, err := os.ReadFile(path) - if err != nil { - t.Fatalf("read selector keys: %v", err) - } - var want []string - if err := json.Unmarshal(body, &want); err != nil { - t.Fatalf("decode selector keys: %v", err) - } - if got := SelectorKeys(); !slices.Equal(got, want) { - t.Errorf("native key list\n got=%v\nwant=%v", got, want) +} + +// An author who hits this on Android and again on web must read one sentence, +// not two dialects of it. +func TestUnknownSelectorKeyMessageMatchesTheCrossRuntimeText(t *testing.T) { + golden := loadSelectorKeysGolden(t) + if got := UnknownSelectorKeyMessage(golden.UnknownKeyExample); got != golden.UnknownKeyMessage { + t.Errorf("native message\n got=%q\nwant=%q", got, golden.UnknownKeyMessage) } } @@ -37,3 +44,20 @@ func TestSelectorKeysAreSorted(t *testing.T) { t.Errorf("keys must stay sorted so the two lists compare readably: %v", keys) } } + +func loadSelectorKeysGolden(t *testing.T) selectorKeysGolden { + t.Helper() + path, err := filepath.Abs("../../pkg/spec/test/fixtures/selector-keys.json") + if err != nil { + t.Fatal(err) + } + body, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read selector keys: %v", err) + } + var golden selectorKeysGolden + if err := json.Unmarshal(body, &golden); err != nil { + t.Fatalf("decode selector keys: %v", err) + } + return golden +} diff --git a/pkg/spec/src/web-runtime.ts b/pkg/spec/src/web-runtime.ts index e088a58..f4c60f1 100644 --- a/pkg/spec/src/web-runtime.ts +++ b/pkg/spec/src/web-runtime.ts @@ -694,6 +694,7 @@ export const __testing__ = { selectorFromString, selectorFromObject, SELECTOR_KEYS, + unknownSelectorKeyMessage, xpathStringLiteral, }; diff --git a/pkg/spec/test/fixtures/selector-keys.json b/pkg/spec/test/fixtures/selector-keys.json index 57886b1..87b1bf7 100644 --- a/pkg/spec/test/fixtures/selector-keys.json +++ b/pkg/spec/test/fixtures/selector-keys.json @@ -1,38 +1,44 @@ -[ - "accessibilityIdentifier", - "accessibilityLabel", - "accessibilityText", - "aria-label", - "ariaLabel", - "bounds", - "checked", - "class", - "className", - "clickable", - "content-desc", - "contentDescription", - "data-testid", - "desc", - "descPrefix", - "editable", - "elementType", - "enabled", - "focused", - "hintText", - "id", - "idPrefix", - "identifier", - "label", - "package", - "placeholder", - "placeholderValue", - "resource-id", - "scrollable", - "selected", - "tag", - "testID", - "testTag", - "text", - "title", - "value" -] +{ + "keys": [ + "accessibilityIdentifier", + "accessibilityLabel", + "accessibilityText", + "aria-label", + "ariaLabel", + "bounds", + "checked", + "class", + "className", + "clickable", + "content-desc", + "contentDescription", + "data-testid", + "desc", + "descPrefix", + "editable", + "elementType", + "enabled", + "focused", + "hintText", + "id", + "idPrefix", + "identifier", + "label", + "package", + "placeholder", + "placeholderValue", + "resource-id", + "scrollable", + "selected", + "tag", + "testID", + "testTag", + "text", + "title", + "value" + ], + "unknownKeyExample": [ + "descripton" + ], + "unknownKeyMessage": "selector key \"descripton\" cannot match: no element carries that attribute, and it is not one of the accepted keys: accessibilityIdentifier, accessibilityLabel, accessibilityText, aria-label, ariaLabel, bounds, checked, class, className, clickable, content-desc, contentDescription, data-testid, desc, descPrefix, editable, elementType, enabled, focused, hintText, id, idPrefix, identifier, label, package, placeholder, placeholderValue, resource-id, scrollable, selected, tag, testID, testTag, text, title, value" +} diff --git a/pkg/spec/test/selector-keys.test.ts b/pkg/spec/test/selector-keys.test.ts index 715292e..7f39aae 100644 --- a/pkg/spec/test/selector-keys.test.ts +++ b/pkg/spec/test/selector-keys.test.ts @@ -5,10 +5,14 @@ import { test } from "node:test"; import { __testing__ } from "../src/web-runtime.ts"; -const { SELECTOR_KEYS } = __testing__; +const { SELECTOR_KEYS, unknownSelectorKeyMessage } = __testing__; const goldenPath = fileURLToPath(new URL("./fixtures/selector-keys.json", import.meta.url)); -const golden: string[] = JSON.parse(readFileSync(goldenPath, "utf8")); +const golden: { + keys: string[]; + unknownKeyExample: string[]; + unknownKeyMessage: string; +} = JSON.parse(readFileSync(goldenPath, "utf8")); // Both runtimes reject an object-selector key they do not know, so the two key // lists have to be one list. Were they to drift, a spec would be accepted by @@ -17,5 +21,11 @@ const golden: string[] = JSON.parse(readFileSync(goldenPath, "utf8")); // internal/hierarchy/selector_keys_test.go asserts the SAME file from the // native side. test("the web key list is the cross-runtime list", () => { - assert.deepEqual([...SELECTOR_KEYS], golden); + assert.deepEqual([...SELECTOR_KEYS], golden.keys); +}); + +// An author who hits this on Android and again on web must read one sentence, +// not two dialects of it. +test("the web diagnostic is the cross-runtime text", () => { + assert.equal(unknownSelectorKeyMessage(golden.unknownKeyExample), golden.unknownKeyMessage); });