diff --git a/.mapping.json b/.mapping.json index db4939deb..d7e9b7a01 100644 --- a/.mapping.json +++ b/.mapping.json @@ -26845,6 +26845,7 @@ "client/web/divkit/tests/hermione/screens/regression/Visible_in_div-base/Click_on_visible/chromeMobile/visible.png":"divkit/public/client/web/divkit/tests/hermione/screens/regression/Visible_in_div-base/Click_on_visible/chromeMobile/visible.png", "client/web/divkit/tests/hermione/screens/regression/Visible_in_div-base/Click_on_visible/firefoxMobile/visible.png":"divkit/public/client/web/divkit/tests/hermione/screens/regression/Visible_in_div-base/Click_on_visible/firefoxMobile/visible.png", "client/web/divkit/tests/hermione/static/index.html":"divkit/public/client/web/divkit/tests/hermione/static/index.html", + "client/web/divkit/tests/parsing/parsing.test.ts":"divkit/public/client/web/divkit/tests/parsing/parsing.test.ts", "client/web/divkit/tests/templates/__snapshots__/array.test.ts.snap":"divkit/public/client/web/divkit/tests/templates/__snapshots__/array.test.ts.snap", "client/web/divkit/tests/templates/__snapshots__/array_of_nested_items.test.ts.snap":"divkit/public/client/web/divkit/tests/templates/__snapshots__/array_of_nested_items.test.ts.snap", "client/web/divkit/tests/templates/__snapshots__/array_with_transform.test.ts.snap":"divkit/public/client/web/divkit/tests/templates/__snapshots__/array_with_transform.test.ts.snap", diff --git a/client/web/divkit/tests/parsing/parsing.test.ts b/client/web/divkit/tests/parsing/parsing.test.ts new file mode 100644 index 000000000..80593b4ee --- /dev/null +++ b/client/web/divkit/tests/parsing/parsing.test.ts @@ -0,0 +1,80 @@ +/* eslint-disable max-depth */ +import * as path from 'node:path'; +import * as fs from 'node:fs'; +import { + describe, + expect, + test +} from 'vitest'; +import { applyTemplate } from '../../src/utils/applyTemplate'; +import type { TemplateContext } from '../../typings/common'; + +const dir = path.resolve(__filename, '../../../../../../test_data/parsing_test_data/templates'); + +function applyTemplates(json: any, templates: Record, context: TemplateContext) { + if (!(json.type && json.type in templates)) { + return json; + } + + const result = applyTemplate(json, context, templates || {}, error => { + throw error; + }); + + const walk = (obj: any, context: TemplateContext) => { + if (obj && typeof obj === 'object') { + if (obj.type && obj.type in templates) { + const res = applyTemplate(obj, context, templates, error => { + throw error; + }); + + return walk(res.json, res.templateContext); + } + + const copy: any = Array.isArray(obj) ? [] : {}; + for (const key in obj) { + if (key === 'items' || Array.isArray(obj)) { + copy[key] = walk(obj[key], context); + } else { + copy[key] = obj[key]; + } + } + return copy; + } + + return obj; + }; + + return walk(result.json, result.templateContext); +} + +function runCase(name: string, itemPath: string): void { + const json = JSON.parse(fs.readFileSync(itemPath, 'utf8')); + + if (!json.platforms || json.platforms.includes('web')) { + test(name.replace('.json', ''), () => { + const cardJson = json.card.states[0].div; + + const result = applyTemplates(cardJson, json.templates || {}, {}); + + expect(result).toMatchObject(json.expected.card.states[0].div); + }); + } +} + +function proc(dir: string): void { + const items = fs.readdirSync(dir); + for (const item of items) { + const itemPath = path.join(dir, item); + if (fs.statSync(itemPath).isDirectory()) { + describe(item, () => { + proc(itemPath); + }); + } else { + runCase(item, itemPath); + } + } +} + +describe('parsing', () => { + proc(dir); +}); diff --git a/test_data/parsing_test_data/templates/definition_literal_wins_over_reference.json b/test_data/parsing_test_data/templates/definition_literal_wins_over_reference.json index 10b5574f1..07f2c043f 100644 --- a/test_data/parsing_test_data/templates/definition_literal_wins_over_reference.json +++ b/test_data/parsing_test_data/templates/definition_literal_wins_over_reference.json @@ -1,5 +1,9 @@ { "description": "A template definition declares both a literal value and a reference definition for the same field. Verifies the literal wins — the reference substitution is skipped when the field is already populated — even when the usage supplies a reference value.", + "platforms": [ + "android", + "ios" + ], "templates": { "conflicted": { "type": "text", diff --git a/test_data/parsing_test_data/templates/outer_instance_overrides_body_default.json b/test_data/parsing_test_data/templates/outer_instance_overrides_body_default.json index c485e0d74..bcbee4757 100644 --- a/test_data/parsing_test_data/templates/outer_instance_overrides_body_default.json +++ b/test_data/parsing_test_data/templates/outer_instance_overrides_body_default.json @@ -1,5 +1,9 @@ { "description": "A reference declared on the leaf template resolves against a reference value declared at two levels: a default on the usage site inside an intermediate template's body, and a card-level override on the outer instance. Verifies the card-level value cascades down through template-body content and overrides the intermediate body's default.", + "platforms": [ + "android", + "ios" + ], "card": { "log_id": "test", "states": [ diff --git a/test_data/parsing_test_data/templates/reference_chain_two_hop_nested_in_body.json b/test_data/parsing_test_data/templates/reference_chain_two_hop_nested_in_body.json index 0eb5a6688..2172a8eb3 100644 --- a/test_data/parsing_test_data/templates/reference_chain_two_hop_nested_in_body.json +++ b/test_data/parsing_test_data/templates/reference_chain_two_hop_nested_in_body.json @@ -1,5 +1,9 @@ { "description": "A two-hop reference chain via nested usage: a template usage inside another template's body declares its own reference definition that points to a card field, while the used template has its own reference definition for a different field (with a literal default). Verifies the two-hop chain does not propagate and the affected item is dropped with an error.", + "platforms": [ + "android", + "ios" + ], "templates": { "base_text": { "type": "text", diff --git a/test_data/parsing_test_data/templates/reference_chain_two_hop_via_usage.json b/test_data/parsing_test_data/templates/reference_chain_two_hop_via_usage.json index 00850a5a3..02dac5fb8 100644 --- a/test_data/parsing_test_data/templates/reference_chain_two_hop_via_usage.json +++ b/test_data/parsing_test_data/templates/reference_chain_two_hop_via_usage.json @@ -1,5 +1,9 @@ { "description": "A template usage inside another template's body declares its own reference definition that points to a field on the outer instance, and the used template has its own reference definition for the same slot. Verifies that a two-hop reference chain (instance field → usage reference → definition reference) does not propagate: only the single-hop sibling resolves, the chained item is dropped with an error.", + "platforms": [ + "android", + "ios" + ], "templates": { "header": { "type": "text", diff --git a/test_data/parsing_test_data/templates/validator_replaces_invalid_field_value.json b/test_data/parsing_test_data/templates/validator_replaces_invalid_field_value.json index 8cf929ca3..5c0e6c037 100644 --- a/test_data/parsing_test_data/templates/validator_replaces_invalid_field_value.json +++ b/test_data/parsing_test_data/templates/validator_replaces_invalid_field_value.json @@ -1,5 +1,9 @@ { "description": "Not a template test — verifies that an invalid field value (div-text.font_size = -1) is replaced by the field's default and reported as an error.", + "platforms": [ + "android", + "ios" + ], "card": { "log_id": "test", "states": [