@immense/vue-pom-generator 1.0.38 → 1.0.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +953 -165
- package/RELEASE_NOTES.md +20 -33
- package/class-generation/index.ts +233 -30
- package/dist/class-generation/index.d.ts +8 -10
- package/dist/class-generation/index.d.ts.map +1 -1
- package/dist/index.cjs +170 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +170 -19
- package/dist/index.mjs.map +1 -1
- package/dist/plugin/support/build-plugin.d.ts +1 -0
- package/dist/plugin/support/build-plugin.d.ts.map +1 -1
- package/dist/plugin/support/dev-plugin.d.ts +1 -0
- package/dist/plugin/support/dev-plugin.d.ts.map +1 -1
- package/dist/plugin/support-plugins.d.ts +1 -0
- package/dist/plugin/support-plugins.d.ts.map +1 -1
- package/dist/plugin/types.d.ts +3 -1
- package/dist/plugin/types.d.ts.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -26,10 +26,10 @@ const path = require("node:path");
|
|
|
26
26
|
const process = require("node:process");
|
|
27
27
|
const node_url = require("node:url");
|
|
28
28
|
const fs = require("node:fs");
|
|
29
|
+
const parser = require("@babel/parser");
|
|
29
30
|
const jsdom = require("jsdom");
|
|
30
31
|
const compilerCore = require("@vue/compiler-core");
|
|
31
32
|
const types = require("@babel/types");
|
|
32
|
-
const parser = require("@babel/parser");
|
|
33
33
|
const node_perf_hooks = require("node:perf_hooks");
|
|
34
34
|
const compilerDom = require("@vue/compiler-dom");
|
|
35
35
|
const compilerSfc = require("@vue/compiler-sfc");
|
|
@@ -2095,9 +2095,6 @@ Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} templat
|
|
|
2095
2095
|
})();
|
|
2096
2096
|
const tryMergeWithExistingPrimary = (candidateActionName) => {
|
|
2097
2097
|
const mergeKey = (args.pomMergeKey ?? "").trim();
|
|
2098
|
-
if (isKeyed) {
|
|
2099
|
-
return false;
|
|
2100
|
-
}
|
|
2101
2098
|
const existingEntry = primaryByActionName.get(candidateActionName);
|
|
2102
2099
|
const existingPom = existingEntry?.pom;
|
|
2103
2100
|
if (!existingEntry || !existingPom) {
|
|
@@ -2108,6 +2105,9 @@ Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} templat
|
|
|
2108
2105
|
...existingPom.alternateFormattedDataTestIds ?? []
|
|
2109
2106
|
];
|
|
2110
2107
|
const sharesSelectorIdentity = existingSelectors.includes(formattedDataTestIdForPom);
|
|
2108
|
+
if (isKeyed && !sharesSelectorIdentity) {
|
|
2109
|
+
return false;
|
|
2110
|
+
}
|
|
2111
2111
|
if (!mergeKey && !sharesSelectorIdentity) {
|
|
2112
2112
|
return false;
|
|
2113
2113
|
}
|
|
@@ -2154,6 +2154,11 @@ Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} templat
|
|
|
2154
2154
|
conflicts = false;
|
|
2155
2155
|
}
|
|
2156
2156
|
}
|
|
2157
|
+
if (conflicts && nameCollisionBehavior === "error" && tryMergeWithExistingPrimary(actionName)) {
|
|
2158
|
+
methodName = candidate;
|
|
2159
|
+
mergedIntoExisting = true;
|
|
2160
|
+
break;
|
|
2161
|
+
}
|
|
2157
2162
|
if (conflicts && nameCollisionBehavior === "error") {
|
|
2158
2163
|
const roleSuffix = upperFirst(normalizedRole || "Element");
|
|
2159
2164
|
const baseNameUpper = upperFirst(baseWithSuffix);
|
|
@@ -2200,11 +2205,6 @@ Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} templat
|
|
|
2200
2205
|
reservedMembers.add(actionName);
|
|
2201
2206
|
break;
|
|
2202
2207
|
}
|
|
2203
|
-
if (nameCollisionBehavior === "error" && tryMergeWithExistingPrimary(actionName)) {
|
|
2204
|
-
methodName = candidate;
|
|
2205
|
-
mergedIntoExisting = true;
|
|
2206
|
-
break;
|
|
2207
|
-
}
|
|
2208
2208
|
if (!collisionDetails) {
|
|
2209
2209
|
collisionDetails = { getterName: chosenGetterName, actionName };
|
|
2210
2210
|
collisionHint = hint || (args.semanticNameHint ?? "").trim() || null;
|
|
@@ -3781,6 +3781,7 @@ function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options =
|
|
|
3781
3781
|
chunks.push(" {");
|
|
3782
3782
|
if (pom.formattedDataTestId.includes("${") || allTestIds.length <= 1) {
|
|
3783
3783
|
chunks.push(` await ${locatorName}${pom.formattedDataTestId.includes("${") ? `(${args})` : ""}.ClickAsync();`);
|
|
3784
|
+
chunks.push(` return new ${target}(Page);`);
|
|
3784
3785
|
} else {
|
|
3785
3786
|
chunks.push(" Exception? lastError = null;");
|
|
3786
3787
|
chunks.push(` foreach (var testId in new[] { ${allTestIds.map(toCSharpTestIdExpression).join(", ")} })`);
|
|
@@ -3801,7 +3802,6 @@ function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options =
|
|
|
3801
3802
|
chunks.push(" }");
|
|
3802
3803
|
chunks.push(' throw lastError ?? new System.Exception("[pom] Failed to navigate using any candidate test id.");');
|
|
3803
3804
|
}
|
|
3804
|
-
chunks.push(` return new ${target}(Page);`);
|
|
3805
3805
|
chunks.push(" }");
|
|
3806
3806
|
chunks.push("");
|
|
3807
3807
|
continue;
|
|
@@ -4058,6 +4058,7 @@ function generateViewObjectModelContent(componentName, dependencies, componentHi
|
|
|
4058
4058
|
};
|
|
4059
4059
|
const customPomClassIdentifierMap = options.customPomClassIdentifierMap ?? {};
|
|
4060
4060
|
const customPomAvailableClassIdentifiers = options.customPomAvailableClassIdentifiers ?? /* @__PURE__ */ new Set();
|
|
4061
|
+
const customPomMethodSignaturesByClass = options.customPomMethodSignaturesByClass ?? /* @__PURE__ */ new Map();
|
|
4061
4062
|
const attachmentsForThisClass = customPomAttachments.filter((a) => {
|
|
4062
4063
|
if (!Object.prototype.hasOwnProperty.call(customPomClassIdentifierMap, a.className))
|
|
4063
4064
|
return false;
|
|
@@ -4068,7 +4069,9 @@ function generateViewObjectModelContent(componentName, dependencies, componentHi
|
|
|
4068
4069
|
return a.attachWhenUsesComponents.some((c) => hasChildComponent(c));
|
|
4069
4070
|
}).map((a) => ({
|
|
4070
4071
|
className: customPomClassIdentifierMap[a.className],
|
|
4071
|
-
propertyName: a.propertyName
|
|
4072
|
+
propertyName: a.propertyName,
|
|
4073
|
+
flatten: a.flatten ?? false,
|
|
4074
|
+
methodSignatures: a.flatten ? customPomMethodSignaturesByClass.get(a.className) ?? /* @__PURE__ */ new Map() : /* @__PURE__ */ new Map()
|
|
4072
4075
|
}));
|
|
4073
4076
|
let content = "";
|
|
4074
4077
|
const sourceRel = toPosixRelativePath(outputDir, filePath);
|
|
@@ -4109,6 +4112,15 @@ export class ${className} extends BasePage {
|
|
|
4109
4112
|
`;
|
|
4110
4113
|
const widgetInstances = isView ? getWidgetInstancesForView(componentName, dependencies.dataTestIdSet, customPomAvailableClassIdentifiers) : [];
|
|
4111
4114
|
const componentRefsForInstances = isView ? usedComponentSet?.size ? usedComponentSet : childrenComponentSet : childrenComponentSet;
|
|
4115
|
+
const childInstancePropertyNames = Array.from(componentRefsForInstances).filter((child) => componentHierarchyMap.has(child) && componentHierarchyMap.get(child)?.dataTestIdSet.size).map((child) => child.split(".vue")[0]);
|
|
4116
|
+
const blockedViewPassthroughMethodNames = new Set(
|
|
4117
|
+
attachmentsForThisClass.filter((a) => a.flatten).flatMap((a) => Array.from(a.methodSignatures.keys()))
|
|
4118
|
+
);
|
|
4119
|
+
const reservedAttachmentPassthroughNames = /* @__PURE__ */ new Set([
|
|
4120
|
+
...attachmentsForThisClass.map((a) => a.propertyName),
|
|
4121
|
+
...widgetInstances.map((w) => w.propertyName),
|
|
4122
|
+
...childInstancePropertyNames
|
|
4123
|
+
]);
|
|
4112
4124
|
if (isView && (componentRefsForInstances.size > 0 || attachmentsForThisClass.length > 0 || widgetInstances.length > 0)) {
|
|
4113
4125
|
content += getComponentInstances(componentRefsForInstances, componentHierarchyMap, attachmentsForThisClass, widgetInstances);
|
|
4114
4126
|
content += getConstructor(componentRefsForInstances, componentHierarchyMap, attachmentsForThisClass, widgetInstances, { testIdAttribute });
|
|
@@ -4117,8 +4129,9 @@ export class ${className} extends BasePage {
|
|
|
4117
4129
|
content += getComponentInstances(/* @__PURE__ */ new Set(), componentHierarchyMap, attachmentsForThisClass);
|
|
4118
4130
|
content += getConstructor(/* @__PURE__ */ new Set(), componentHierarchyMap, attachmentsForThisClass, [], { testIdAttribute });
|
|
4119
4131
|
}
|
|
4132
|
+
content += getAttachmentPassthroughMethods(componentName, dependencies, attachmentsForThisClass, reservedAttachmentPassthroughNames);
|
|
4120
4133
|
if (isView && componentRefsForInstances.size === 1) {
|
|
4121
|
-
content += getViewPassthroughMethods(componentName, dependencies, componentRefsForInstances, componentHierarchyMap);
|
|
4134
|
+
content += getViewPassthroughMethods(componentName, dependencies, componentRefsForInstances, componentHierarchyMap, blockedViewPassthroughMethodNames);
|
|
4122
4135
|
}
|
|
4123
4136
|
if (isView && options.vueRouterFluentChaining) {
|
|
4124
4137
|
const routeMeta = options.routeMetaByComponent?.[componentName] ?? null;
|
|
@@ -4129,7 +4142,7 @@ export class ${className} extends BasePage {
|
|
|
4129
4142
|
content += "}\n";
|
|
4130
4143
|
return content;
|
|
4131
4144
|
}
|
|
4132
|
-
function getViewPassthroughMethods(viewName, viewDependencies, childrenComponentSet, componentHierarchyMap) {
|
|
4145
|
+
function getViewPassthroughMethods(viewName, viewDependencies, childrenComponentSet, componentHierarchyMap, blockedMethodNames = /* @__PURE__ */ new Set()) {
|
|
4133
4146
|
const existingOnView = viewDependencies.generatedMethods ?? /* @__PURE__ */ new Map();
|
|
4134
4147
|
const methodToChildren = /* @__PURE__ */ new Map();
|
|
4135
4148
|
for (const child of childrenComponentSet) {
|
|
@@ -4143,7 +4156,7 @@ function getViewPassthroughMethods(viewName, viewDependencies, childrenComponent
|
|
|
4143
4156
|
for (const [name, sig] of methods.entries()) {
|
|
4144
4157
|
if (!sig)
|
|
4145
4158
|
continue;
|
|
4146
|
-
if (existingOnView.has(name))
|
|
4159
|
+
if (existingOnView.has(name) || blockedMethodNames.has(name))
|
|
4147
4160
|
continue;
|
|
4148
4161
|
const list = methodToChildren.get(name) ?? [];
|
|
4149
4162
|
list.push({ childProp, params: sig.params, argNames: sig.argNames });
|
|
@@ -4174,6 +4187,130 @@ function getViewPassthroughMethods(viewName, viewDependencies, childrenComponent
|
|
|
4174
4187
|
""
|
|
4175
4188
|
].join("\n");
|
|
4176
4189
|
}
|
|
4190
|
+
function getAttachmentPassthroughMethods(ownerName, ownerDependencies, attachmentsForThisClass, reservedMemberNames) {
|
|
4191
|
+
if (!attachmentsForThisClass.some((a) => a.flatten && a.methodSignatures.size > 0)) {
|
|
4192
|
+
return "";
|
|
4193
|
+
}
|
|
4194
|
+
const existingOnClass = ownerDependencies.generatedMethods ?? /* @__PURE__ */ new Map();
|
|
4195
|
+
const methodToAttachments = /* @__PURE__ */ new Map();
|
|
4196
|
+
for (const attachment of attachmentsForThisClass) {
|
|
4197
|
+
if (!attachment.flatten) {
|
|
4198
|
+
continue;
|
|
4199
|
+
}
|
|
4200
|
+
for (const [methodName, signature] of attachment.methodSignatures.entries()) {
|
|
4201
|
+
if (methodName === "constructor" || existingOnClass.has(methodName) || reservedMemberNames.has(methodName)) {
|
|
4202
|
+
continue;
|
|
4203
|
+
}
|
|
4204
|
+
const list = methodToAttachments.get(methodName) ?? [];
|
|
4205
|
+
list.push({
|
|
4206
|
+
propertyName: attachment.propertyName,
|
|
4207
|
+
params: signature.params,
|
|
4208
|
+
argNames: signature.argNames
|
|
4209
|
+
});
|
|
4210
|
+
methodToAttachments.set(methodName, list);
|
|
4211
|
+
}
|
|
4212
|
+
}
|
|
4213
|
+
const sorted = Array.from(methodToAttachments.entries()).sort((a, b) => a[0].localeCompare(b[0]));
|
|
4214
|
+
const lines = [];
|
|
4215
|
+
for (const [methodName, candidates] of sorted) {
|
|
4216
|
+
if (candidates.length !== 1) {
|
|
4217
|
+
continue;
|
|
4218
|
+
}
|
|
4219
|
+
const { propertyName, params, argNames } = candidates[0];
|
|
4220
|
+
const callArgs = argNames.join(", ");
|
|
4221
|
+
const invocation = callArgs ? `this.${propertyName}.${methodName}(${callArgs})` : `this.${propertyName}.${methodName}()`;
|
|
4222
|
+
lines.push(
|
|
4223
|
+
"",
|
|
4224
|
+
` ${methodName}(${params}) {`,
|
|
4225
|
+
` return ${invocation};`,
|
|
4226
|
+
" }"
|
|
4227
|
+
);
|
|
4228
|
+
}
|
|
4229
|
+
if (!lines.length) {
|
|
4230
|
+
return "";
|
|
4231
|
+
}
|
|
4232
|
+
return [
|
|
4233
|
+
"",
|
|
4234
|
+
` // Passthrough methods composed from custom helper attachments of ${ownerName}.`,
|
|
4235
|
+
...lines,
|
|
4236
|
+
""
|
|
4237
|
+
].join("\n");
|
|
4238
|
+
}
|
|
4239
|
+
function sliceNodeSource(source, node) {
|
|
4240
|
+
if (node.start == null || node.end == null) {
|
|
4241
|
+
return null;
|
|
4242
|
+
}
|
|
4243
|
+
const snippet = source.slice(node.start, node.end).trim();
|
|
4244
|
+
return snippet.length ? snippet : null;
|
|
4245
|
+
}
|
|
4246
|
+
function getCustomPomCallArgumentName(param) {
|
|
4247
|
+
if (param.type === "Identifier") {
|
|
4248
|
+
return param.name;
|
|
4249
|
+
}
|
|
4250
|
+
if (param.type === "AssignmentPattern") {
|
|
4251
|
+
return param.left.type === "Identifier" ? param.left.name : null;
|
|
4252
|
+
}
|
|
4253
|
+
if (param.type === "RestElement") {
|
|
4254
|
+
return param.argument.type === "Identifier" ? `...${param.argument.name}` : null;
|
|
4255
|
+
}
|
|
4256
|
+
return null;
|
|
4257
|
+
}
|
|
4258
|
+
function extractCustomPomMethodSignatures(source, exportName) {
|
|
4259
|
+
const signatures = /* @__PURE__ */ new Map();
|
|
4260
|
+
let ast;
|
|
4261
|
+
try {
|
|
4262
|
+
ast = parser.parse(source, {
|
|
4263
|
+
sourceType: "module",
|
|
4264
|
+
plugins: ["typescript", "jsx"]
|
|
4265
|
+
});
|
|
4266
|
+
} catch {
|
|
4267
|
+
return signatures;
|
|
4268
|
+
}
|
|
4269
|
+
for (const statement of ast.program.body) {
|
|
4270
|
+
if (statement.type !== "ExportNamedDeclaration" || !statement.declaration || statement.declaration.type !== "ClassDeclaration") {
|
|
4271
|
+
continue;
|
|
4272
|
+
}
|
|
4273
|
+
const declaration = statement.declaration;
|
|
4274
|
+
if (declaration.id?.name !== exportName) {
|
|
4275
|
+
continue;
|
|
4276
|
+
}
|
|
4277
|
+
for (const member of declaration.body.body) {
|
|
4278
|
+
if (member.type !== "ClassMethod" || member.kind !== "method" || member.static || member.computed) {
|
|
4279
|
+
continue;
|
|
4280
|
+
}
|
|
4281
|
+
if (member.accessibility === "private" || member.accessibility === "protected") {
|
|
4282
|
+
continue;
|
|
4283
|
+
}
|
|
4284
|
+
if (member.key.type !== "Identifier") {
|
|
4285
|
+
continue;
|
|
4286
|
+
}
|
|
4287
|
+
const params = [];
|
|
4288
|
+
const argNames = [];
|
|
4289
|
+
let supported = true;
|
|
4290
|
+
member.params.forEach((param) => {
|
|
4291
|
+
if (!supported) {
|
|
4292
|
+
return;
|
|
4293
|
+
}
|
|
4294
|
+
const paramSource = sliceNodeSource(source, param);
|
|
4295
|
+
const argName = getCustomPomCallArgumentName(param);
|
|
4296
|
+
if (!paramSource || !argName) {
|
|
4297
|
+
supported = false;
|
|
4298
|
+
return;
|
|
4299
|
+
}
|
|
4300
|
+
params.push(paramSource);
|
|
4301
|
+
argNames.push(argName);
|
|
4302
|
+
});
|
|
4303
|
+
if (!supported) {
|
|
4304
|
+
continue;
|
|
4305
|
+
}
|
|
4306
|
+
signatures.set(member.key.name, {
|
|
4307
|
+
params: params.join(", "),
|
|
4308
|
+
argNames
|
|
4309
|
+
});
|
|
4310
|
+
}
|
|
4311
|
+
}
|
|
4312
|
+
return signatures;
|
|
4313
|
+
}
|
|
4177
4314
|
function ensureDir(dir) {
|
|
4178
4315
|
const normalized = dir.replace(/\\/g, "/");
|
|
4179
4316
|
if (!fs.existsSync(normalized)) {
|
|
@@ -4217,6 +4354,7 @@ async function generateAggregatedFiles(componentHierarchyMap, vueFilesPathMap, b
|
|
|
4217
4354
|
]);
|
|
4218
4355
|
const usedImportIdentifiers = /* @__PURE__ */ new Set();
|
|
4219
4356
|
const customPomClassIdentifierMap2 = {};
|
|
4357
|
+
const customPomMethodSignaturesByClass2 = /* @__PURE__ */ new Map();
|
|
4220
4358
|
const ensureUniqueIdentifier = (base2) => {
|
|
4221
4359
|
let candidate = base2;
|
|
4222
4360
|
let i = 2;
|
|
@@ -4230,7 +4368,10 @@ async function generateAggregatedFiles(componentHierarchyMap, vueFilesPathMap, b
|
|
|
4230
4368
|
const customDirRelOrAbs = options.customPomDir ?? "tests/playwright/pom/custom";
|
|
4231
4369
|
const customDirAbs = path.isAbsolute(customDirRelOrAbs) ? customDirRelOrAbs : path.resolve(projectRoot, customDirRelOrAbs);
|
|
4232
4370
|
if (!fs.existsSync(customDirAbs)) {
|
|
4233
|
-
return
|
|
4371
|
+
return {
|
|
4372
|
+
classIdentifierMap: customPomClassIdentifierMap2,
|
|
4373
|
+
methodSignaturesByClass: customPomMethodSignaturesByClass2
|
|
4374
|
+
};
|
|
4234
4375
|
}
|
|
4235
4376
|
const files = fs.readdirSync(customDirAbs).filter((f) => f.endsWith(".ts")).sort((a, b) => a.localeCompare(b));
|
|
4236
4377
|
for (const file of files) {
|
|
@@ -4252,8 +4393,12 @@ Fix by setting generation.playwright.customPoms.importAliases["${exportName}"] t
|
|
|
4252
4393
|
} else {
|
|
4253
4394
|
localIdentifier = ensureUniqueIdentifier(requested);
|
|
4254
4395
|
}
|
|
4255
|
-
customPomClassIdentifierMap2[exportName] = localIdentifier;
|
|
4256
4396
|
const customFileAbs = path.join(customDirAbs, file);
|
|
4397
|
+
customPomClassIdentifierMap2[exportName] = localIdentifier;
|
|
4398
|
+
const customPomMethodSignatures = extractCustomPomMethodSignatures(fs.readFileSync(customFileAbs, "utf8"), exportName);
|
|
4399
|
+
if (customPomMethodSignatures.size > 0) {
|
|
4400
|
+
customPomMethodSignaturesByClass2.set(exportName, customPomMethodSignatures);
|
|
4401
|
+
}
|
|
4257
4402
|
const fromOutputDir = outputDir;
|
|
4258
4403
|
const importPath = stripExtension(toPosixRelativePath(fromOutputDir, customFileAbs));
|
|
4259
4404
|
if (localIdentifier !== exportName) {
|
|
@@ -4262,10 +4407,15 @@ Fix by setting generation.playwright.customPoms.importAliases["${exportName}"] t
|
|
|
4262
4407
|
imports.push(`import { ${exportName} } from "${importPath}";`);
|
|
4263
4408
|
}
|
|
4264
4409
|
}
|
|
4265
|
-
return
|
|
4410
|
+
return {
|
|
4411
|
+
classIdentifierMap: customPomClassIdentifierMap2,
|
|
4412
|
+
methodSignaturesByClass: customPomMethodSignaturesByClass2
|
|
4413
|
+
};
|
|
4266
4414
|
};
|
|
4267
|
-
const
|
|
4268
|
-
const
|
|
4415
|
+
const customPomImportResolution = addCustomPomImports();
|
|
4416
|
+
const customPomClassIdentifierMap = customPomImportResolution?.classIdentifierMap ?? {};
|
|
4417
|
+
const customPomMethodSignaturesByClass = customPomImportResolution?.methodSignaturesByClass ?? /* @__PURE__ */ new Map();
|
|
4418
|
+
const customPomAvailableClassIdentifiers = new Set(Object.values(customPomClassIdentifierMap));
|
|
4269
4419
|
const referencedTargets = /* @__PURE__ */ new Set();
|
|
4270
4420
|
for (const [, deps] of items) {
|
|
4271
4421
|
for (const dt of deps.dataTestIdSet) {
|
|
@@ -4419,6 +4569,7 @@ Fix by setting generation.playwright.customPoms.importAliases["${exportName}"] t
|
|
|
4419
4569
|
customPomAttachments: options.customPomAttachments ?? [],
|
|
4420
4570
|
customPomClassIdentifierMap,
|
|
4421
4571
|
customPomAvailableClassIdentifiers,
|
|
4572
|
+
customPomMethodSignaturesByClass,
|
|
4422
4573
|
testIdAttribute: options.testIdAttribute,
|
|
4423
4574
|
vueRouterFluentChaining: options.vueRouterFluentChaining,
|
|
4424
4575
|
routeMetaByComponent: options.routeMetaByComponent
|