mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
fix(vue): correctly determine leaving view when transitioning to a new instance of a previous page (#22655)
resolves #22654 resolves #22658
This commit is contained in:
21
packages/vue-router/package-lock.json
generated
21
packages/vue-router/package-lock.json
generated
@@ -8,10 +8,6 @@
|
||||
"name": "@ionic/vue-router",
|
||||
"version": "5.5.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ionic/core": "file:../../core",
|
||||
"path-to-regexp": "^6.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ionic/vue": "5.4.1",
|
||||
"@types/jest": "^26.0.13",
|
||||
@@ -28,6 +24,7 @@
|
||||
},
|
||||
"../../core": {
|
||||
"version": "5.4.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ionicons": "^5.1.2",
|
||||
@@ -5849,11 +5846,6 @@
|
||||
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.0.tgz",
|
||||
"integrity": "sha512-f66KywYG6+43afgE/8j/GoiNyygk/bnoCbps++3ErRKsIYkGGupyv07R2Ok5m9i67Iqc+T2g1eAUGUPzWhYTyg=="
|
||||
},
|
||||
"node_modules/performance-now": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||
@@ -11490,7 +11482,8 @@
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz",
|
||||
"integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"jest-regex-util": {
|
||||
"version": "26.0.0",
|
||||
@@ -12623,11 +12616,6 @@
|
||||
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
|
||||
"dev": true
|
||||
},
|
||||
"path-to-regexp": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.0.tgz",
|
||||
"integrity": "sha512-f66KywYG6+43afgE/8j/GoiNyygk/bnoCbps++3ErRKsIYkGGupyv07R2Ok5m9i67Iqc+T2g1eAUGUPzWhYTyg=="
|
||||
},
|
||||
"performance-now": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||
@@ -14204,7 +14192,8 @@
|
||||
"version": "7.4.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.0.tgz",
|
||||
"integrity": "sha512-kyFwXuV/5ymf+IXhS6f0+eAFvydbaBW3zjpT6hUdAh/hbVjTIB5EHBGi0bPoCLSK2wcuz3BrEkB9LrYv1Nm4NQ==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"xml-name-validator": {
|
||||
"version": "3.0.0",
|
||||
|
||||
@@ -66,8 +66,5 @@
|
||||
"json",
|
||||
"jsx"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"path-to-regexp": "^6.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export const createRouter = (opts: IonicVueRouterOptions) => {
|
||||
|
||||
const router = createVueRouter(routerOptions);
|
||||
const ionRouter = createIonRouter(opts, router);
|
||||
const viewStacks = createViewStacks();
|
||||
const viewStacks = createViewStacks(router);
|
||||
|
||||
const oldInstall = router.install.bind(router);
|
||||
router.install = (app: App) => {
|
||||
|
||||
@@ -1,622 +0,0 @@
|
||||
// @ts-nocheck
|
||||
// https://github.com/pillarjs/path-to-regexp
|
||||
// Included here so we do not need to add additional package.json dependency
|
||||
|
||||
/**
|
||||
* Tokenizer results.
|
||||
*/
|
||||
interface LexToken {
|
||||
type:
|
||||
| "OPEN"
|
||||
| "CLOSE"
|
||||
| "PATTERN"
|
||||
| "NAME"
|
||||
| "CHAR"
|
||||
| "ESCAPED_CHAR"
|
||||
| "MODIFIER"
|
||||
| "END";
|
||||
index: number;
|
||||
value: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tokenize input string.
|
||||
*/
|
||||
function lexer(str: string): LexToken[] {
|
||||
const tokens: LexToken[] = [];
|
||||
let i = 0;
|
||||
|
||||
while (i < str.length) {
|
||||
const char = str[i];
|
||||
|
||||
if (char === "*" || char === "+" || char === "?") {
|
||||
tokens.push({ type: "MODIFIER", index: i, value: str[i++] });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === "\\") {
|
||||
tokens.push({ type: "ESCAPED_CHAR", index: i++, value: str[i++] });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === "{") {
|
||||
tokens.push({ type: "OPEN", index: i, value: str[i++] });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === "}") {
|
||||
tokens.push({ type: "CLOSE", index: i, value: str[i++] });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === ":") {
|
||||
let name = "";
|
||||
let j = i + 1;
|
||||
|
||||
while (j < str.length) {
|
||||
const code = str.charCodeAt(j);
|
||||
|
||||
if (
|
||||
// `0-9`
|
||||
(code >= 48 && code <= 57) ||
|
||||
// `A-Z`
|
||||
(code >= 65 && code <= 90) ||
|
||||
// `a-z`
|
||||
(code >= 97 && code <= 122) ||
|
||||
// `_`
|
||||
code === 95
|
||||
) {
|
||||
name += str[j++];
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (!name) throw new TypeError(`Missing parameter name at ${i}`);
|
||||
|
||||
tokens.push({ type: "NAME", index: i, value: name });
|
||||
i = j;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === "(") {
|
||||
let count = 1;
|
||||
let pattern = "";
|
||||
let j = i + 1;
|
||||
|
||||
if (str[j] === "?") {
|
||||
throw new TypeError(`Pattern cannot start with "?" at ${j}`);
|
||||
}
|
||||
|
||||
while (j < str.length) {
|
||||
if (str[j] === "\\") {
|
||||
pattern += str[j++] + str[j++];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str[j] === ")") {
|
||||
count--;
|
||||
if (count === 0) {
|
||||
j++;
|
||||
break;
|
||||
}
|
||||
} else if (str[j] === "(") {
|
||||
count++;
|
||||
if (str[j + 1] !== "?") {
|
||||
throw new TypeError(`Capturing groups are not allowed at ${j}`);
|
||||
}
|
||||
}
|
||||
|
||||
pattern += str[j++];
|
||||
}
|
||||
|
||||
if (count) throw new TypeError(`Unbalanced pattern at ${i}`);
|
||||
if (!pattern) throw new TypeError(`Missing pattern at ${i}`);
|
||||
|
||||
tokens.push({ type: "PATTERN", index: i, value: pattern });
|
||||
i = j;
|
||||
continue;
|
||||
}
|
||||
|
||||
tokens.push({ type: "CHAR", index: i, value: str[i++] });
|
||||
}
|
||||
|
||||
tokens.push({ type: "END", index: i, value: "" });
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
export interface ParseOptions {
|
||||
/**
|
||||
* Set the default delimiter for repeat parameters. (default: `'/'`)
|
||||
*/
|
||||
delimiter?: string;
|
||||
/**
|
||||
* List of characters to automatically consider prefixes when parsing.
|
||||
*/
|
||||
prefixes?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a string for the raw tokens.
|
||||
*/
|
||||
export function parse(str: string, options: ParseOptions = {}): Token[] {
|
||||
const tokens = lexer(str);
|
||||
const { prefixes = "./" } = options;
|
||||
const defaultPattern = `[^${escapeString(options.delimiter || "/#?")}]+?`;
|
||||
const result: Token[] = [];
|
||||
let key = 0;
|
||||
let i = 0;
|
||||
let path = "";
|
||||
|
||||
const tryConsume = (type: LexToken["type"]): string | undefined => {
|
||||
if (i < tokens.length && tokens[i].type === type) return tokens[i++].value;
|
||||
};
|
||||
|
||||
const mustConsume = (type: LexToken["type"]): string => {
|
||||
const value = tryConsume(type);
|
||||
if (value !== undefined) return value;
|
||||
const { type: nextType, index } = tokens[i];
|
||||
throw new TypeError(`Unexpected ${nextType} at ${index}, expected ${type}`);
|
||||
};
|
||||
|
||||
const consumeText = (): string => {
|
||||
let result = "";
|
||||
let value: string | undefined;
|
||||
// tslint:disable-next-line
|
||||
while ((value = tryConsume("CHAR") || tryConsume("ESCAPED_CHAR"))) {
|
||||
result += value;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
while (i < tokens.length) {
|
||||
const char = tryConsume("CHAR");
|
||||
const name = tryConsume("NAME");
|
||||
const pattern = tryConsume("PATTERN");
|
||||
|
||||
if (name || pattern) {
|
||||
let prefix = char || "";
|
||||
|
||||
if (prefixes.indexOf(prefix) === -1) {
|
||||
path += prefix;
|
||||
prefix = "";
|
||||
}
|
||||
|
||||
if (path) {
|
||||
result.push(path);
|
||||
path = "";
|
||||
}
|
||||
|
||||
result.push({
|
||||
name: name || key++,
|
||||
prefix,
|
||||
suffix: "",
|
||||
pattern: pattern || defaultPattern,
|
||||
modifier: tryConsume("MODIFIER") || ""
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = char || tryConsume("ESCAPED_CHAR");
|
||||
if (value) {
|
||||
path += value;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (path) {
|
||||
result.push(path);
|
||||
path = "";
|
||||
}
|
||||
|
||||
const open = tryConsume("OPEN");
|
||||
if (open) {
|
||||
const prefix = consumeText();
|
||||
const name = tryConsume("NAME") || "";
|
||||
const pattern = tryConsume("PATTERN") || "";
|
||||
const suffix = consumeText();
|
||||
|
||||
mustConsume("CLOSE");
|
||||
|
||||
result.push({
|
||||
name: name || (pattern ? key++ : ""),
|
||||
pattern: name && !pattern ? defaultPattern : pattern,
|
||||
prefix,
|
||||
suffix,
|
||||
modifier: tryConsume("MODIFIER") || ""
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
mustConsume("END");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export interface TokensToFunctionOptions {
|
||||
/**
|
||||
* When `true` the regexp will be case sensitive. (default: `false`)
|
||||
*/
|
||||
sensitive?: boolean;
|
||||
/**
|
||||
* Function for encoding input strings for output.
|
||||
*/
|
||||
encode?: (value: string, token: Key) => string;
|
||||
/**
|
||||
* When `false` the function can produce an invalid (unmatched) path. (default: `true`)
|
||||
*/
|
||||
validate?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile a string to a template function for the path.
|
||||
*/
|
||||
export function compile<P extends object = object>(
|
||||
str: string,
|
||||
options?: ParseOptions & TokensToFunctionOptions
|
||||
) {
|
||||
return tokensToFunction<P>(parse(str, options), options);
|
||||
}
|
||||
|
||||
export type PathFunction<P extends object = object> = (data?: P) => string;
|
||||
|
||||
/**
|
||||
* Expose a method for transforming tokens into the path function.
|
||||
*/
|
||||
export function tokensToFunction<P extends object = object>(
|
||||
tokens: Token[],
|
||||
options: TokensToFunctionOptions = {}
|
||||
): PathFunction<P> {
|
||||
const reFlags = flags(options);
|
||||
const { encode = (x: string) => x, validate = true } = options;
|
||||
|
||||
// Compile all the tokens into regexps.
|
||||
const matches = tokens.map(token => {
|
||||
if (typeof token === "object") {
|
||||
return new RegExp(`^(?:${token.pattern})$`, reFlags);
|
||||
}
|
||||
});
|
||||
|
||||
return (data: Record<string, any> | null | undefined) => {
|
||||
let path = "";
|
||||
|
||||
for (let i = 0; i < tokens.length; i++) {
|
||||
const token = tokens[i];
|
||||
|
||||
if (typeof token === "string") {
|
||||
path += token;
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = data ? data[token.name] : undefined;
|
||||
const optional = token.modifier === "?" || token.modifier === "*";
|
||||
const repeat = token.modifier === "*" || token.modifier === "+";
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
if (!repeat) {
|
||||
throw new TypeError(
|
||||
`Expected "${token.name}" to not repeat, but got an array`
|
||||
);
|
||||
}
|
||||
|
||||
if (value.length === 0) {
|
||||
if (optional) continue;
|
||||
|
||||
throw new TypeError(`Expected "${token.name}" to not be empty`);
|
||||
}
|
||||
|
||||
for (let j = 0; j < value.length; j++) {
|
||||
const segment = encode(value[j], token);
|
||||
|
||||
if (validate && !(matches[i] as RegExp).test(segment)) {
|
||||
throw new TypeError(
|
||||
`Expected all "${token.name}" to match "${token.pattern}", but got "${segment}"`
|
||||
);
|
||||
}
|
||||
|
||||
path += token.prefix + segment + token.suffix;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof value === "string" || typeof value === "number") {
|
||||
const segment = encode(String(value), token);
|
||||
|
||||
if (validate && !(matches[i] as RegExp).test(segment)) {
|
||||
throw new TypeError(
|
||||
`Expected "${token.name}" to match "${token.pattern}", but got "${segment}"`
|
||||
);
|
||||
}
|
||||
|
||||
path += token.prefix + segment + token.suffix;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (optional) continue;
|
||||
|
||||
const typeOfMessage = repeat ? "an array" : "a string";
|
||||
throw new TypeError(`Expected "${token.name}" to be ${typeOfMessage}`);
|
||||
}
|
||||
|
||||
return path;
|
||||
};
|
||||
}
|
||||
|
||||
export interface RegexpToFunctionOptions {
|
||||
/**
|
||||
* Function for decoding strings for params.
|
||||
*/
|
||||
decode?: (value: string, token: Key) => string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A match result contains data about the path match.
|
||||
*/
|
||||
export interface MatchResult<P extends object = object> {
|
||||
path: string;
|
||||
index: number;
|
||||
params: P;
|
||||
}
|
||||
|
||||
/**
|
||||
* A match is either `false` (no match) or a match result.
|
||||
*/
|
||||
export type Match<P extends object = object> = false | MatchResult<P>;
|
||||
|
||||
/**
|
||||
* The match function takes a string and returns whether it matched the path.
|
||||
*/
|
||||
export type MatchFunction<P extends object = object> = (
|
||||
path: string
|
||||
) => Match<P>;
|
||||
|
||||
/**
|
||||
* Create path match function from `path-to-regexp` spec.
|
||||
*/
|
||||
export function match<P extends object = object>(
|
||||
str: Path,
|
||||
options?: ParseOptions & TokensToRegexpOptions & RegexpToFunctionOptions
|
||||
) {
|
||||
const keys: Key[] = [];
|
||||
const re = pathToRegexp(str, keys, options);
|
||||
return regexpToFunction<P>(re, keys, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a path match function from `path-to-regexp` output.
|
||||
*/
|
||||
export function regexpToFunction<P extends object = object>(
|
||||
re: RegExp,
|
||||
keys: Key[],
|
||||
options: RegexpToFunctionOptions = {}
|
||||
): MatchFunction<P> {
|
||||
const { decode = (x: string) => x } = options;
|
||||
|
||||
return function(pathname: string) {
|
||||
const m = re.exec(pathname);
|
||||
if (!m) return false;
|
||||
|
||||
const { 0: path, index } = m;
|
||||
const params = Object.create(null);
|
||||
|
||||
for (let i = 1; i < m.length; i++) {
|
||||
// tslint:disable-next-line
|
||||
if (m[i] === undefined) continue;
|
||||
|
||||
const key = keys[i - 1];
|
||||
|
||||
if (key.modifier === "*" || key.modifier === "+") {
|
||||
params[key.name] = m[i].split(key.prefix + key.suffix).map(value => {
|
||||
return decode(value, key);
|
||||
});
|
||||
} else {
|
||||
params[key.name] = decode(m[i], key);
|
||||
}
|
||||
}
|
||||
|
||||
return { path, index, params };
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape a regular expression string.
|
||||
*/
|
||||
function escapeString(str: string) {
|
||||
return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, "\\$1");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the flags for a regexp from the options.
|
||||
*/
|
||||
function flags(options?: { sensitive?: boolean }) {
|
||||
return options && options.sensitive ? "" : "i";
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata about a key.
|
||||
*/
|
||||
export interface Key {
|
||||
name: string | number;
|
||||
prefix: string;
|
||||
suffix: string;
|
||||
pattern: string;
|
||||
modifier: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A token is a string (nothing special) or key metadata (capture group).
|
||||
*/
|
||||
export type Token = string | Key;
|
||||
|
||||
/**
|
||||
* Pull out keys from a regexp.
|
||||
*/
|
||||
function regexpToRegexp(path: RegExp, keys?: Key[]): RegExp {
|
||||
if (!keys) return path;
|
||||
|
||||
const groupsRegex = /\((?:\?<(.*?)>)?(?!\?)/g;
|
||||
|
||||
let index = 0;
|
||||
let execResult = groupsRegex.exec(path.source);
|
||||
while (execResult) {
|
||||
keys.push({
|
||||
// Use parenthesized substring match if available, index otherwise
|
||||
name: execResult[1] || index++,
|
||||
prefix: "",
|
||||
suffix: "",
|
||||
modifier: "",
|
||||
pattern: ""
|
||||
});
|
||||
execResult = groupsRegex.exec(path.source);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform an array into a regexp.
|
||||
*/
|
||||
function arrayToRegexp(
|
||||
paths: Array<string | RegExp>,
|
||||
keys?: Key[],
|
||||
options?: TokensToRegexpOptions & ParseOptions
|
||||
): RegExp {
|
||||
const parts = paths.map(path => pathToRegexp(path, keys, options).source);
|
||||
return new RegExp(`(?:${parts.join("|")})`, flags(options));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a path regexp from string input.
|
||||
*/
|
||||
function stringToRegexp(
|
||||
path: string,
|
||||
keys?: Key[],
|
||||
options?: TokensToRegexpOptions & ParseOptions
|
||||
) {
|
||||
return tokensToRegexp(parse(path, options), keys, options);
|
||||
}
|
||||
|
||||
export interface TokensToRegexpOptions {
|
||||
/**
|
||||
* When `true` the regexp will be case sensitive. (default: `false`)
|
||||
*/
|
||||
sensitive?: boolean;
|
||||
/**
|
||||
* When `true` the regexp won't allow an optional trailing delimiter to match. (default: `false`)
|
||||
*/
|
||||
strict?: boolean;
|
||||
/**
|
||||
* When `true` the regexp will match to the end of the string. (default: `true`)
|
||||
*/
|
||||
end?: boolean;
|
||||
/**
|
||||
* When `true` the regexp will match from the beginning of the string. (default: `true`)
|
||||
*/
|
||||
start?: boolean;
|
||||
/**
|
||||
* Sets the final character for non-ending optimistic matches. (default: `/`)
|
||||
*/
|
||||
delimiter?: string;
|
||||
/**
|
||||
* List of characters that can also be "end" characters.
|
||||
*/
|
||||
endsWith?: string;
|
||||
/**
|
||||
* Encode path tokens for use in the `RegExp`.
|
||||
*/
|
||||
encode?: (value: string) => string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expose a function for taking tokens and returning a RegExp.
|
||||
*/
|
||||
export function tokensToRegexp(
|
||||
tokens: Token[],
|
||||
keys?: Key[],
|
||||
options: TokensToRegexpOptions = {}
|
||||
) {
|
||||
const {
|
||||
strict = false,
|
||||
start = true,
|
||||
end = true,
|
||||
encode = (x: string) => x
|
||||
} = options;
|
||||
const endsWith = `[${escapeString(options.endsWith || "")}]|$`;
|
||||
const delimiter = `[${escapeString(options.delimiter || "/#?")}]`;
|
||||
let route = start ? "^" : "";
|
||||
|
||||
// Iterate over the tokens and create our regexp string.
|
||||
for (const token of tokens) {
|
||||
if (typeof token === "string") {
|
||||
route += escapeString(encode(token));
|
||||
} else {
|
||||
const prefix = escapeString(encode(token.prefix));
|
||||
const suffix = escapeString(encode(token.suffix));
|
||||
|
||||
if (token.pattern) {
|
||||
if (keys) keys.push(token);
|
||||
|
||||
if (prefix || suffix) {
|
||||
if (token.modifier === "+" || token.modifier === "*") {
|
||||
const mod = token.modifier === "*" ? "?" : "";
|
||||
route += `(?:${prefix}((?:${token.pattern})(?:${suffix}${prefix}(?:${token.pattern}))*)${suffix})${mod}`;
|
||||
} else {
|
||||
route += `(?:${prefix}(${token.pattern})${suffix})${token.modifier}`;
|
||||
}
|
||||
} else {
|
||||
route += `(${token.pattern})${token.modifier}`;
|
||||
}
|
||||
} else {
|
||||
route += `(?:${prefix}${suffix})${token.modifier}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (end) {
|
||||
if (!strict) route += `${delimiter}?`;
|
||||
|
||||
route += !options.endsWith ? "$" : `(?=${endsWith})`;
|
||||
} else {
|
||||
const endToken = tokens[tokens.length - 1];
|
||||
const isEndDelimited =
|
||||
typeof endToken === "string"
|
||||
? delimiter.indexOf(endToken[endToken.length - 1]) > -1
|
||||
: // tslint:disable-next-line
|
||||
endToken === undefined;
|
||||
|
||||
if (!strict) {
|
||||
route += `(?:${delimiter}(?=${endsWith}))?`;
|
||||
}
|
||||
|
||||
if (!isEndDelimited) {
|
||||
route += `(?=${delimiter}|${endsWith})`;
|
||||
}
|
||||
}
|
||||
|
||||
return new RegExp(route, flags(options));
|
||||
}
|
||||
|
||||
/**
|
||||
* Supported `path-to-regexp` input types.
|
||||
*/
|
||||
export type Path = string | RegExp | Array<string | RegExp>;
|
||||
|
||||
/**
|
||||
* Normalize the given path string, returning a regular expression.
|
||||
*
|
||||
* An empty array can be passed in for the keys, which will hold the
|
||||
* placeholder key descriptions. For example, using `/user/:id`, `keys` will
|
||||
* contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`.
|
||||
*/
|
||||
export function pathToRegexp(
|
||||
path: Path,
|
||||
keys?: Key[],
|
||||
options?: TokensToRegexpOptions & ParseOptions
|
||||
) {
|
||||
if (path instanceof RegExp) return regexpToRegexp(path, keys);
|
||||
if (Array.isArray(path)) return arrayToRegexp(path, keys, options);
|
||||
return stringToRegexp(path, keys, options);
|
||||
}
|
||||
@@ -188,6 +188,7 @@ export const createIonRouter = (opts: IonicVueRouterOptions, router: Router) =>
|
||||
pathname: location.path,
|
||||
search: location.fullPath && location.fullPath.split('?')[1] || '',
|
||||
params: location.params && location.params,
|
||||
prevRouteLastPathname: leavingLocationInfo.lastPathname
|
||||
}
|
||||
|
||||
if (isPushed) {
|
||||
@@ -205,6 +206,7 @@ export const createIonRouter = (opts: IonicVueRouterOptions, router: Router) =>
|
||||
routeInfo.pushedByRoute = currentRouteInfo?.pushedByRoute || routeInfo.pushedByRoute;
|
||||
routeInfo.routerDirection = currentRouteInfo?.routerDirection || routeInfo.routerDirection;
|
||||
routeInfo.routerAnimation = currentRouteInfo?.routerAnimation || routeInfo.routerAnimation;
|
||||
routeInfo.prevRouteLastPathname = currentRouteInfo?.lastPathname;
|
||||
}
|
||||
|
||||
locationHistory.add(routeInfo);
|
||||
|
||||
@@ -20,6 +20,7 @@ export interface RouteInfo {
|
||||
routerDirection?: RouteDirection;
|
||||
routerAnimation?: AnimationBuilder;
|
||||
lastPathname?: string;
|
||||
prevRouteLastPathname?: string;
|
||||
pathname?: string;
|
||||
search?: string;
|
||||
params?: { [k: string]: any };
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { generateId } from './utils';
|
||||
import { pathToRegexp } from './regexp';
|
||||
import { RouteInfo,
|
||||
ViewItem,
|
||||
ViewStacks,
|
||||
} from './types';
|
||||
import { RouteLocationMatched } from 'vue-router';
|
||||
import { RouteLocationMatched, Router } from 'vue-router';
|
||||
import { shallowRef } from 'vue';
|
||||
|
||||
export const createViewStacks = () => {
|
||||
export const createViewStacks = (router: Router) => {
|
||||
let viewStacks: ViewStacks = {};
|
||||
|
||||
const clear = (outletId: number) => {
|
||||
@@ -20,14 +19,19 @@ export const createViewStacks = () => {
|
||||
|
||||
const registerIonPage = (viewItem: ViewItem, ionPage: HTMLElement) => {
|
||||
viewItem.ionPageElement = ionPage;
|
||||
viewItem.ionRoute = true;
|
||||
}
|
||||
|
||||
const findViewItemByRouteInfo = (routeInfo: RouteInfo, outletId?: number) => {
|
||||
return findViewItemByPath(routeInfo.pathname, outletId);
|
||||
}
|
||||
|
||||
const findLeavingViewItemByRouteInfo = (routeInfo: RouteInfo, outletId?: number) => {
|
||||
return findViewItemByPath(routeInfo.lastPathname, outletId, false);
|
||||
const findLeavingViewItemByRouteInfo = (routeInfo: RouteInfo, outletId?: number, mustBeIonRoute: boolean = true) => {
|
||||
return findViewItemByPath(routeInfo.lastPathname, outletId, mustBeIonRoute);
|
||||
}
|
||||
|
||||
const findViewItemByPathname = (pathname: string, outletId?: number) => {
|
||||
return findViewItemByPath(pathname, outletId);
|
||||
}
|
||||
|
||||
const findViewItemInStack = (path: string, stack: ViewItem[]): ViewItem | undefined => {
|
||||
@@ -40,30 +44,31 @@ export const createViewStacks = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const findViewItemByPath = (path: string, outletId?: number, strict: boolean = true): ViewItem | undefined => {
|
||||
const findViewItemByPath = (path: string, outletId?: number, mustBeIonRoute: boolean = false): ViewItem | undefined => {
|
||||
const matchView = (viewItem: ViewItem) => {
|
||||
const pathname = path;
|
||||
const viewItemPath = viewItem.matchedRoute.path;
|
||||
if (
|
||||
(mustBeIonRoute && !viewItem.ionRoute) ||
|
||||
path === ''
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const regexp = pathToRegexp(viewItemPath, [], {
|
||||
end: viewItem.exact,
|
||||
strict: viewItem.exact,
|
||||
sensitive: false
|
||||
});
|
||||
return (regexp.exec(pathname)) ? viewItem : undefined;
|
||||
const resolvedPath = router.resolve(path);
|
||||
const findMatchedRoute = resolvedPath.matched.find((matchedRoute: RouteLocationMatched) => matchedRoute === viewItem.matchedRoute && (path === viewItem.pathname || matchedRoute.path.includes(':')));
|
||||
|
||||
if (findMatchedRoute) {
|
||||
return viewItem;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (outletId) {
|
||||
const stack = viewStacks[outletId];
|
||||
if (!stack) return undefined;
|
||||
|
||||
const quickMatch = findViewItemInStack(path, stack);
|
||||
if (quickMatch) return quickMatch;
|
||||
|
||||
if (!strict) {
|
||||
const match = stack.find(matchView);
|
||||
if (match) return match;
|
||||
}
|
||||
const match = (router) ? stack.find(matchView) : findViewItemInStack(path, stack)
|
||||
if (match) return match;
|
||||
} else {
|
||||
for (let outletId in viewStacks) {
|
||||
const stack = viewStacks[outletId];
|
||||
@@ -125,6 +130,7 @@ export const createViewStacks = () => {
|
||||
clear,
|
||||
findViewItemByRouteInfo,
|
||||
findLeavingViewItemByRouteInfo,
|
||||
findViewItemByPathname,
|
||||
createViewItem,
|
||||
getChildrenToRender,
|
||||
add,
|
||||
|
||||
@@ -76,14 +76,14 @@ export const IonRouterOutlet = defineComponent({
|
||||
* to make sure the view is in the outlet we want.
|
||||
*/
|
||||
const routeInfo = ionRouter.getCurrentRouteInfo();
|
||||
const enteringViewItem = viewStacks.findViewItemByRouteInfo({ pathname: routeInfo.pushedByRoute }, id);
|
||||
const enteringViewItem = viewStacks.findViewItemByRouteInfo({ pathname: routeInfo.pushedByRoute || '' }, id);
|
||||
|
||||
return !!enteringViewItem;
|
||||
}
|
||||
const onStart = async () => {
|
||||
const routeInfo = ionRouter.getCurrentRouteInfo();
|
||||
const { routerAnimation } = routeInfo;
|
||||
const enteringViewItem = viewStacks.findViewItemByRouteInfo({ pathname: routeInfo.pushedByRoute }, id);
|
||||
const enteringViewItem = viewStacks.findViewItemByRouteInfo({ pathname: routeInfo.pushedByRoute || '' }, id);
|
||||
const leavingViewItem = viewStacks.findViewItemByRouteInfo(routeInfo, id);
|
||||
|
||||
if (leavingViewItem) {
|
||||
@@ -139,7 +139,7 @@ export const IonRouterOutlet = defineComponent({
|
||||
* re-hide the page that was going to enter.
|
||||
*/
|
||||
const routeInfo = ionRouter.getCurrentRouteInfo();
|
||||
const enteringViewItem = viewStacks.findViewItemByRouteInfo({ pathname: routeInfo.pushedByRoute }, id);
|
||||
const enteringViewItem = viewStacks.findViewItemByRouteInfo({ pathname: routeInfo.pushedByRoute || '' }, id);
|
||||
enteringViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
|
||||
enteringViewItem.ionPageElement.classList.add('ion-page-hidden');
|
||||
}
|
||||
@@ -192,17 +192,21 @@ export const IonRouterOutlet = defineComponent({
|
||||
|
||||
const handlePageTransition = async () => {
|
||||
const routeInfo = ionRouter.getCurrentRouteInfo();
|
||||
const { routerDirection, routerAction, routerAnimation } = routeInfo;
|
||||
const { routerDirection, routerAction, routerAnimation, prevRouteLastPathname } = routeInfo;
|
||||
|
||||
const enteringViewItem = viewStacks.findViewItemByRouteInfo(routeInfo, id);
|
||||
const leavingViewItem = viewStacks.findLeavingViewItemByRouteInfo(routeInfo, id);
|
||||
let leavingViewItem = viewStacks.findLeavingViewItemByRouteInfo(routeInfo, id);
|
||||
const enteringEl = enteringViewItem.ionPageElement;
|
||||
|
||||
if (enteringViewItem === leavingViewItem) return;
|
||||
|
||||
if (!leavingViewItem && prevRouteLastPathname) {
|
||||
leavingViewItem = viewStacks.findViewItemByPathname(prevRouteLastPathname, id);
|
||||
}
|
||||
|
||||
fireLifecycle(enteringViewItem.vueComponent, enteringViewItem.vueComponentRef, LIFECYCLE_WILL_ENTER);
|
||||
|
||||
if (leavingViewItem) {
|
||||
if (leavingViewItem && enteringViewItem !== leavingViewItem) {
|
||||
let animationBuilder = routerAnimation;
|
||||
const leavingEl = leavingViewItem.ionPageElement;
|
||||
|
||||
|
||||
@@ -68,6 +68,10 @@ const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: 'two',
|
||||
component: () => import('@/views/NestedChildTwo.vue')
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
component: () => import('@/views/Folder.vue')
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -135,4 +139,6 @@ const router = createRouter({
|
||||
routes
|
||||
});
|
||||
|
||||
(window as any).debugRouter = router;
|
||||
|
||||
export default router
|
||||
|
||||
82
packages/vue/test-app/src/views/Folder.vue
Normal file
82
packages/vue/test-app/src/views/Folder.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<ion-page data-pageid="folder">
|
||||
<ion-header :translucent="true">
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-menu-button></ion-menu-button>
|
||||
</ion-buttons>
|
||||
<ion-title>{{ folder }}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content :fullscreen="true">
|
||||
<ion-header collapse="condense">
|
||||
<ion-toolbar>
|
||||
<ion-title size="large">{{ folder }}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<div id="container">
|
||||
<strong class="capitalize">{{ folder }}</strong>
|
||||
<p>Explore <a target="_blank" rel="noopener noreferrer" href="https://ionicframework.com/docs/components">UI Components</a></p>
|
||||
</div>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { IonButtons, IonContent, IonHeader, IonMenuButton, IonPage, IonTitle, IonToolbar } from '@ionic/vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { ref, computed, watch } from 'vue';
|
||||
|
||||
export default {
|
||||
name: 'Folder',
|
||||
components: {
|
||||
IonButtons,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonMenuButton,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
},
|
||||
setup() {
|
||||
const route = useRoute();
|
||||
const folder = ref(route.params.id || 'Inbox');
|
||||
const matchedFolder = computed(() => route.params.id);
|
||||
|
||||
watch(matchedFolder, () => {
|
||||
folder.value = matchedFolder.value as string;
|
||||
})
|
||||
|
||||
return { folder }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#container {
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
#container strong {
|
||||
font-size: 20px;
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
#container p {
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
color: #8c8c8c;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#container a {
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
@@ -14,37 +14,37 @@
|
||||
</ion-header>
|
||||
|
||||
<ion-list>
|
||||
<ion-item router-link="/overlays">
|
||||
<ion-item button router-link="/overlays">
|
||||
<ion-label>Overlays</ion-label>
|
||||
</ion-item>
|
||||
<ion-item router-link="/inputs">
|
||||
<ion-item button router-link="/inputs">
|
||||
<ion-label>Inputs</ion-label>
|
||||
</ion-item>
|
||||
<ion-item router-link="/slides">
|
||||
<ion-item button router-link="/slides">
|
||||
<ion-label>Slides</ion-label>
|
||||
</ion-item>
|
||||
<ion-item router-link="/navigation" id="navigation">
|
||||
<ion-item button router-link="/navigation" id="navigation">
|
||||
<ion-label>Navigation</ion-label>
|
||||
</ion-item>
|
||||
<ion-item router-link="/routing" id="routing">
|
||||
<ion-item button router-link="/routing" id="routing">
|
||||
<ion-label>Routing</ion-label>
|
||||
</ion-item>
|
||||
<ion-item router-link="/default-href" id="default-href">
|
||||
<ion-item button router-link="/default-href" id="default-href">
|
||||
<ion-label>Default Href</ion-label>
|
||||
</ion-item>
|
||||
<ion-item router-link="/nested" id="nested">
|
||||
<ion-item button router-link="/nested" id="nested">
|
||||
<ion-label>Nested Router Outlet</ion-label>
|
||||
</ion-item>
|
||||
<ion-item router-link="/tabs" id="tabs">
|
||||
<ion-item button router-link="/tabs" id="tabs">
|
||||
<ion-label>Tabs</ion-label>
|
||||
</ion-item>
|
||||
<ion-item router-link="/tabs-secondary" id="tab-secondary">
|
||||
<ion-item button router-link="/tabs-secondary" id="tab-secondary">
|
||||
<ion-label>Tabs Secondary</ion-label>
|
||||
</ion-item>
|
||||
<ion-item router-link="/lifecycle" id="lifecycle">
|
||||
<ion-item button router-link="/lifecycle" id="lifecycle">
|
||||
<ion-label>Lifecycle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item router-link="/delayed-redirect" id="delayed-redirect">
|
||||
<ion-item button router-link="/delayed-redirect" id="delayed-redirect">
|
||||
<ion-label>Delayed Redirect</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
<template>
|
||||
<ion-page data-pageid="routeroutlet">
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-button id="inbox" router-link="/nested/inbox" router-direction="root">Inbox</ion-button>
|
||||
<ion-button id="trash" router-link="/nested/trash" router-direction="root">Trash</ion-button>
|
||||
<ion-button id="outbox" router-link="/nested/outbox" router-direction="root">Outbox</ion-button>
|
||||
<ion-button id="spam" router-link="/nested/spam" router-direction="root">Spam</ion-button>
|
||||
<ion-button id="other" router-link="/nested/two" router-direction="root">Other</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-router-outlet></ion-router-outlet>
|
||||
</ion-content>
|
||||
@@ -8,6 +19,10 @@
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
IonHeader,
|
||||
IonButtons,
|
||||
IonButton,
|
||||
IonToolbar,
|
||||
IonContent,
|
||||
IonPage,
|
||||
IonRouterOutlet,
|
||||
@@ -17,6 +32,10 @@ import { defineComponent } from 'vue';
|
||||
export default defineComponent({
|
||||
name: 'RouterOutlet',
|
||||
components: {
|
||||
IonHeader,
|
||||
IonButtons,
|
||||
IonButton,
|
||||
IonToolbar,
|
||||
IonContent,
|
||||
IonPage,
|
||||
IonRouterOutlet
|
||||
|
||||
@@ -39,6 +39,10 @@
|
||||
<ion-item button @click="replace" id="replace">
|
||||
<ion-label>Replace to Navigation page</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item button router-link="/tabs/tab1" id="tab1">
|
||||
<ion-label>Go to /tabs/tab1</ion-label>
|
||||
</ion-item>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
@@ -17,18 +17,18 @@
|
||||
|
||||
<ExploreContainer name="Tab 1 page" />
|
||||
|
||||
<ion-item router-link="/tabs/tab1/child-one" id="child-one">
|
||||
<ion-item button router-link="/tabs/tab1/child-one" id="child-one">
|
||||
<ion-label>Go to Tab 1 Child 1</ion-label>
|
||||
</ion-item>
|
||||
<ion-item router-link="/nested" id="nested">
|
||||
<ion-item button router-link="/nested" id="nested">
|
||||
<ion-label>Go to Nested Outlet</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item router-link="/tabs-secondary" id="tabs-secondary">
|
||||
<ion-item button router-link="/tabs-secondary" id="tabs-secondary">
|
||||
<ion-label>Go to Secondary Tabs</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item router-link="/tabs" id="tabs-primary">
|
||||
<ion-item button router-link="/tabs" id="tabs-primary">
|
||||
<ion-label>Go to Primary Tabs</ion-label>
|
||||
</ion-item>
|
||||
</ion-content>
|
||||
|
||||
@@ -15,17 +15,37 @@
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ExploreContainer name="Tab 2 page" />
|
||||
<ion-item button router-link="/routing" id="routing">
|
||||
<ion-label>Go to /routing</ion-label>
|
||||
</ion-item>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { IonButtons, IonBackButton, IonPage, IonHeader, IonToolbar, IonTitle, IonContent } from '@ionic/vue';
|
||||
import ExploreContainer from '@/components/ExploreContainer.vue';
|
||||
import {
|
||||
IonButtons,
|
||||
IonBackButton,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonPage,
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonTitle,
|
||||
IonContent
|
||||
} from '@ionic/vue';
|
||||
|
||||
export default {
|
||||
name: 'Tab2',
|
||||
components: { IonButtons, IonBackButton, ExploreContainer, IonHeader, IonToolbar, IonTitle, IonContent, IonPage }
|
||||
}
|
||||
export default {
|
||||
components: {
|
||||
IonButtons,
|
||||
IonBackButton,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonPage,
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonTitle,
|
||||
IonContent
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<ion-title size="large">Tab 3</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
|
||||
<ExploreContainer name="Tab 3 page" />
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
@@ -25,4 +25,4 @@ export default {
|
||||
name: 'Tab3',
|
||||
components: { ExploreContainer, IonHeader, IonToolbar, IonTitle, IonContent, IonPage }
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -119,6 +119,63 @@ describe('Routing', () => {
|
||||
cy.ionSwipeToGoBack(true);
|
||||
cy.ionPageVisible('navigation');
|
||||
});
|
||||
|
||||
// Verifies fix for https://github.com/ionic-team/ionic-framework/issues/22654
|
||||
it('should show correct view when navigating between parameter urls', () => {
|
||||
cy.visit('http://localhost:8080/nested');
|
||||
|
||||
cy.ionPageVisible('nestedchild');
|
||||
|
||||
cy.get('[data-pageid="routeroutlet"] #trash').click();
|
||||
cy.ionPageVisible('folder');
|
||||
|
||||
cy.get('[data-pageid="routeroutlet"] #outbox').click();
|
||||
cy.ionPageVisible('folder');
|
||||
|
||||
cy.get('[data-pageid="routeroutlet"] #other').click();
|
||||
cy.ionPageVisible('nestedchildtwo');
|
||||
cy.ionPageDoesNotExist('folder');
|
||||
|
||||
cy.get('[data-pageid="routeroutlet"] #spam').click();
|
||||
cy.ionPageVisible('folder');
|
||||
cy.ionPageDoesNotExist('nestedchildtwo');
|
||||
|
||||
cy.get('[data-pageid="routeroutlet"] #outbox').click();
|
||||
cy.ionPageVisible('folder');
|
||||
|
||||
cy.get('[data-pageid="routeroutlet"] #other').click();
|
||||
cy.ionPageVisible('nestedchildtwo');
|
||||
cy.ionPageDoesNotExist('folder');
|
||||
});
|
||||
|
||||
// Verifies fix for https://github.com/ionic-team/ionic-framework/issues/22658
|
||||
it('should select correct leaving view when navigating between paramter urls', () => {
|
||||
cy.visit('http://localhost:8080');
|
||||
|
||||
cy.routerPush('/routing/123');
|
||||
cy.ionPageVisible('routingparameter');
|
||||
cy.ionPageHidden('home');
|
||||
|
||||
cy.routerPush('/routing/456');
|
||||
cy.ionPageVisible('routingparameter');
|
||||
cy.ionPageHidden('home');
|
||||
|
||||
cy.routerPush('/navigation');
|
||||
cy.ionPageVisible('navigation');
|
||||
cy.ionPageHidden('routingparameter');
|
||||
|
||||
cy.routerPush('/routing/789');
|
||||
cy.ionPageVisible('routingparameter');
|
||||
cy.ionPageHidden('home');
|
||||
|
||||
cy.routerPush('/routing/000');
|
||||
cy.ionPageVisible('routingparameter');
|
||||
cy.ionPageHidden('home');
|
||||
|
||||
cy.routerPush('/navigation');
|
||||
cy.ionPageVisible('navigation');
|
||||
cy.ionPageHidden('routingparameter');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Routing - Swipe to Go Back', () => {
|
||||
|
||||
@@ -85,6 +85,8 @@ describe('Tabs', () => {
|
||||
});
|
||||
|
||||
it('should go back from a tabs page to a non-tabs page using ion-back-button', () => {
|
||||
cy.visit('http://localhost:8080');
|
||||
|
||||
cy.get('#tabs').click();
|
||||
cy.ionPageVisible('tab1');
|
||||
|
||||
@@ -97,6 +99,8 @@ describe('Tabs', () => {
|
||||
});
|
||||
|
||||
it('should properly clear stack when leaving tabs', () => {
|
||||
cy.visit('http://localhost:8080/');
|
||||
|
||||
cy.get('#tabs').click();
|
||||
cy.ionPageVisible('tab1');
|
||||
|
||||
@@ -147,6 +151,28 @@ describe('Tabs', () => {
|
||||
cy.ionPageVisible('tab3-secondary');
|
||||
cy.ionPageHidden('tab1-secondary');
|
||||
});
|
||||
|
||||
// Verifies 1 of 2 fixes for https://github.com/ionic-team/ionic-framework/issues/22519
|
||||
it('should show correct tab when switching between tabbed and non-tabbed contexts', () => {
|
||||
cy.visit('http://localhost:8080/routing');
|
||||
|
||||
cy.get('[data-pageid="routing"] #tab1').click();
|
||||
cy.ionPageHidden('routing');
|
||||
cy.ionPageVisible('tab1');
|
||||
|
||||
cy.get('ion-tab-button#tab-button-tab2').click();
|
||||
cy.ionPageHidden('tab1');
|
||||
cy.ionPageVisible('tab2');
|
||||
|
||||
cy.get('[data-pageid="tab2"] #routing').click();
|
||||
cy.ionPageVisible('routing');
|
||||
cy.ionPageHidden('tabs');
|
||||
|
||||
cy.get('[data-pageid="routing"] #tab1').click();
|
||||
cy.ionPageVisible('tab1');
|
||||
cy.ionPageHidden('routing');
|
||||
cy.ionPageHidden('tab2');
|
||||
})
|
||||
})
|
||||
|
||||
describe('Tabs - Swipe to Go Back', () => {
|
||||
|
||||
@@ -71,3 +71,9 @@ Cypress.Commands.add('ionPageDoesNotExist', (pageId) => {
|
||||
cy.get(`div.ion-page[data-pageid=${pageId}]`)
|
||||
.should('not.exist')
|
||||
});
|
||||
|
||||
Cypress.Commands.add('routerPush', (path) => {
|
||||
cy.window().then(win => {
|
||||
win.debugRouter.push(path);
|
||||
});
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user