mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-01-12 22:51:18 +08:00
fix: throw if the same pattern resolves to multiple screens
This commit is contained in:
@@ -2673,6 +2673,47 @@ it('uses nearest parent wildcard match for unmatched paths', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('throws if two screens map to the same pattern', () => {
|
||||
const path = '/bar/42/baz/test';
|
||||
|
||||
expect(() =>
|
||||
getStateFromPath(path, {
|
||||
screens: {
|
||||
Foo: {
|
||||
screens: {
|
||||
Bar: {
|
||||
path: '/bar/:id/',
|
||||
screens: {
|
||||
Baz: 'baz',
|
||||
},
|
||||
},
|
||||
Bax: '/bar/:id/baz',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
).toThrow(
|
||||
"Found conflicting screens with the same pattern. The pattern 'bar/:id/baz' resolves to both 'Foo > Bax' and 'Foo > Bar > Baz'. Patterns must be unique and cannot resolve to more than one screen."
|
||||
);
|
||||
|
||||
expect(() =>
|
||||
getStateFromPath(path, {
|
||||
screens: {
|
||||
Foo: {
|
||||
screens: {
|
||||
Bar: {
|
||||
path: '/bar/:id/',
|
||||
screens: {
|
||||
Baz: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('throws if wildcard is specified with legacy config', () => {
|
||||
const path = '/bar/42/baz/test';
|
||||
const config = {
|
||||
|
||||
@@ -239,6 +239,10 @@ export default function getPathFromState(
|
||||
// Object.fromEntries is not available in older iOS versions
|
||||
const fromEntries = <K extends string, V>(entries: (readonly [K, V])[]) =>
|
||||
entries.reduce((acc, [k, v]) => {
|
||||
if (acc.hasOwnProperty(k)) {
|
||||
throw new Error(`A value for key '${k}' already exists in the object.`);
|
||||
}
|
||||
|
||||
acc[k] = v;
|
||||
return acc;
|
||||
}, {} as Record<K, V>);
|
||||
|
||||
@@ -119,6 +119,12 @@ export default function getStateFromPath(
|
||||
// - the most exhaustive ones are always at the beginning
|
||||
// - patterns with wildcard are always at the end
|
||||
|
||||
// If 2 patterns are same, move the one with less route names up
|
||||
// This is an error state, so it's only useful for consistent error messages
|
||||
if (a.pattern === b.pattern) {
|
||||
return b.routeNames.join('>').localeCompare(a.routeNames.join('>'));
|
||||
}
|
||||
|
||||
// If one of the patterns starts with the other, it's more exhaustive
|
||||
// So move it up
|
||||
if (a.pattern.startsWith(b.pattern)) {
|
||||
@@ -155,6 +161,35 @@ export default function getStateFromPath(
|
||||
return bWildcardIndex - aWildcardIndex;
|
||||
});
|
||||
|
||||
// Check for duplicate patterns in the config
|
||||
configs.reduce<Record<string, RouteConfig>>((acc, config) => {
|
||||
if (acc[config.pattern]) {
|
||||
const a = acc[config.pattern].routeNames;
|
||||
const b = config.routeNames;
|
||||
|
||||
// It's not a problem if the path string omitted from a inner most screen
|
||||
// For example, it's ok if a path resolves to `A > B > C` or `A > B`
|
||||
const intersects =
|
||||
a.length > b.length
|
||||
? b.every((it, i) => a[i] === it)
|
||||
: a.every((it, i) => b[i] === it);
|
||||
|
||||
if (!intersects) {
|
||||
throw new Error(
|
||||
`Found conflicting screens with the same pattern. The pattern '${
|
||||
config.pattern
|
||||
}' resolves to both '${a.join(' > ')}' and '${b.join(
|
||||
' > '
|
||||
)}'. Patterns must be unique and cannot resolve to more than one screen.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return Object.assign(acc, {
|
||||
[config.pattern]: config,
|
||||
});
|
||||
}, {});
|
||||
|
||||
if (remaining === '/') {
|
||||
// We need to add special handling of empty path so navigation to empty path also works
|
||||
// When handling empty path, we should only look at the root level config
|
||||
|
||||
Reference in New Issue
Block a user