extractor = new EmbeddedJsonExtractor(); } public function test_extracts_flat_object(): void { $html = 'prefix window.cur = Object.assign(window.cur || {}, {"a":1,"b":"x"}); suffix'; $result = $this->extractor->extractAfterMarker($html, 'window.cur = Object.assign('); self::assertSame(['a' => 1, 'b' => 'x'], $result); } public function test_extracts_deeply_nested_structure(): void { $html = 'x = Object.assign({}, {"apiPrefetchCache":[{"response":{"items":[{"id":1,"files":{"hls":"u"}}]}}]});'; $result = $this->extractor->extractAfterMarker($html, 'Object.assign('); self::assertSame(1, $result['apiPrefetchCache'][0]['response']['items'][0]['id']); self::assertSame('u', $result['apiPrefetchCache'][0]['response']['items'][0]['files']['hls']); } public function test_ignores_braces_inside_strings(): void { // A brace and quote inside a string must not unbalance the count. $html = 'm({"msg":"has } and \" escaped","n":2});'; $result = $this->extractor->extractAfterMarker($html, 'm('); self::assertSame('has } and " escaped', $result['msg']); self::assertSame(2, $result['n']); } public function test_extracts_array_root(): void { $html = 'data = assign([], [1, [2, 3], {"k": "v"}]);'; $result = $this->extractor->extractAfterMarker($html, 'assign([], '); self::assertSame([1, [2, 3], ['k' => 'v']], $result); } public function test_throws_when_marker_missing(): void { $this->expectException(ParseFailedException::class); $this->extractor->extractAfterMarker('no marker here', 'window.cur'); } public function test_throws_on_unbalanced_json(): void { $this->expectException(ParseFailedException::class); $this->extractor->extractAfterMarker('m({"a":1', 'm('); } public function test_handles_cyrillic_content(): void { $html = 'window.cur = Object.assign(window.cur || {}, {"title":"Трамп отменяет гранты"});'; $result = $this->extractor->extractAfterMarker($html, 'window.cur = Object.assign('); self::assertSame('Трамп отменяет гранты', $result['title']); } }