feat(verifier): report the registered extractor count

the web path needs it to check the page sent one reading per extractor.
This commit is contained in:
pj committed 2026-08-15 12:45:31 +05:30
1 parent 45b7624280
commit 4b7a9e2fe2
2 files changed
+60

No files matched your search

+51
View File
@@ -1382,3 +1382,54 @@ func TestWithPlatform_IOSReachesPicker(t *testing.T) {
t.Errorf("key = %q, want back (native press-key pool)", action.Key)
}
}
// TestOverrideExtractorValues_EmptyPayloadIsUndefined pins the cross-host
// meaning of a page reading with no value. JSON has no undefined, so the web
// runtime wraps every reading in a {value} envelope and the chrome driver hands
// an absent value through as an empty payload. It has to land here as undefined,
// because that is what PushSnapshot records for a getter that returned undefined
// on native: decoding it as null instead would make `x.current === undefined`
// answer one thing on the native host and another on web, for one spec.
func TestOverrideExtractorValues_EmptyPayloadIsUndefined(t *testing.T) {
verifier := newVerifier(t)
mustLoad(t, verifier, helloSpec)
if err := verifier.PushSnapshot(SnapshotInput{Snapshots: Snapshots{"ledger.balance": json.RawMessage(`42`)}}); err != nil {
t.Fatal(err)
}
if _, err := verifier.OverrideExtractorValues(map[int]json.RawMessage{
0: nil,
1: json.RawMessage(`null`),
}); err != nil {
t.Fatal(err)
}
for _, want := range []struct {
expression string
result bool
}{
{"screen.current === undefined", true},
{"screen.current === null", false},
{"balance.current === null", true},
{"balance.current === undefined", false},
} {
value, err := verifier.runtime.RunString(want.expression)
if err != nil {
t.Fatalf("evaluate %s: %v", want.expression, err)
}
if value.ToBoolean() != want.result {
t.Errorf("a spec reading %s gets %v, want %v", want.expression, value, want.result)
}
}
}
// TestExtractorCount_ReportsEveryRegisteredExtractor keeps the web path's
// completeness check honest: it compares the page's reading count against this
// number, so a count that ignored an extractor would let a partial table
// through.
func TestExtractorCount_ReportsEveryRegisteredExtractor(t *testing.T) {
verifier := newVerifier(t)
mustLoad(t, verifier, helloSpec)
if got := verifier.ExtractorCount(); got != 2 {
t.Errorf("ExtractorCount() = %d, want 2 (helloSpec registers screen and balance)", got)
}
}
+9
View File
@@ -398,6 +398,15 @@ func (v *Verifier) ChangedExtractors() map[string]ExtractorChange {
return changes
}
// ExtractorCount reports how many extractors the spec registered. The web path
// compares it against the number of readings the page sent: a page reporting
// fewer leaves the rest holding goja's dump-derived value while the others hold
// the page's, and a property comparing previous to current across that split
// fires on a healthy app.
func (v *Verifier) ExtractorCount() int {
return len(v.extractors)
}
// OverrideExtractorValues replaces each extractor's `current` slot with a
// caller-supplied value, keyed by registration index. Used by the web tick
// path so extractor bodies that ran in V8 (against the real DOM) drive the