-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Fix running= with an ALL wildcard crashing when no components match #3957
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -443,16 +443,25 @@ function sideUpdate(outputs: SideUpdateOutput, cb: ICallbackPayload) { | |
| let componentId = id, | ||
| propName, | ||
| replacedIds = []; | ||
| let isPatternMatching = false; | ||
|
|
||
| if (id.startsWith('{')) { | ||
| [componentId, propName] = parsePMCId(id); | ||
| replacedIds = replacePMC(componentId, cb, i, getState); | ||
| isPatternMatching = true; | ||
| } else if (id.includes('.')) { | ||
| [componentId, propName] = id.split('.'); | ||
| } | ||
|
|
||
| const props = propName ? {[propName]: value} : value; | ||
|
|
||
| if (isPatternMatching && replacedIds.length === 0) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This early return is the half of the fix that keeps the unresolved pattern ( |
||
| // A wildcard that matches nothing currently rendered. | ||
| // There is no component to update, and `componentId` still | ||
| // holds the unresolved pattern, so it must not be used. | ||
| return acc; | ||
| } | ||
|
|
||
| if (replacedIds.length === 0) { | ||
| acc.push([componentId, props]); | ||
| } else if (replacedIds.length === 1) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| import {expect} from 'chai'; | ||
| import {beforeEach, describe, it} from 'mocha'; | ||
| import {getAllPMCIds, replacePMC} from '../src/actions/patternMatching'; | ||
|
|
||
| // Minimal stand-in for the pieces of the redux state that the pattern | ||
| // matching helpers read. | ||
| function makeState(objs) { | ||
| return {paths: {strs: {}, objs: objs || {}}}; | ||
| } | ||
|
|
||
| // A wildcard entry as produced by crawling the layout: `values` is the list | ||
| // of id values ordered by the (sorted) id keys. | ||
| function entries(keyStr, valueLists) { | ||
| return { | ||
| [keyStr]: valueLists.map((values, i) => ({ | ||
| values, | ||
| path: ['props', 'children', i] | ||
| })) | ||
| }; | ||
| } | ||
|
|
||
| const cb = {parsedChangedPropsIds: [{id: 'home1', type: 'loading'}]}; | ||
|
|
||
| describe('getAllPMCIds', () => { | ||
| it('returns the matching ids when the wildcard key is registered', () => { | ||
| const state = makeState( | ||
| entries('id,type', [ | ||
| ['home1', 'loading'], | ||
| ['home2', 'loading'] | ||
| ]) | ||
| ); | ||
| const result = getAllPMCIds( | ||
| {id: ['ALL'], type: 'loading'}, | ||
| state, | ||
| 'id' | ||
| ); | ||
| expect(result).to.deep.equal([ | ||
| {id: 'home1', type: 'loading'}, | ||
| {id: 'home2', type: 'loading'} | ||
| ]); | ||
| }); | ||
|
|
||
| it('returns an empty list when no component uses that id shape', () => { | ||
| // This is the state on a page that renders none of the wildcard | ||
| // components: `paths.objs['id,type']` was never populated. | ||
| const result = getAllPMCIds( | ||
| {id: ['ALL'], type: 'loading'}, | ||
| makeState({}), | ||
| 'id' | ||
| ); | ||
| expect(result).to.deep.equal([]); | ||
| }); | ||
| }); | ||
|
|
||
| describe('replacePMC', () => { | ||
| let getState; | ||
|
|
||
| beforeEach(() => { | ||
| getState = () => | ||
| makeState( | ||
| entries('id,type', [ | ||
| ['home1', 'loading'], | ||
| ['home2', 'loading'] | ||
| ]) | ||
| ); | ||
| }); | ||
|
|
||
| it('expands ALL to every matching component', () => { | ||
| const result = replacePMC( | ||
| {id: ['ALL'], type: 'loading'}, | ||
| cb, | ||
| 0, | ||
| getState | ||
| ); | ||
| expect(result).to.deep.equal([ | ||
| {id: 'home1', type: 'loading'}, | ||
| {id: 'home2', type: 'loading'} | ||
| ]); | ||
| }); | ||
|
|
||
| it('resolves MATCH against the triggering id', () => { | ||
| const result = replacePMC( | ||
| {id: ['MATCH'], type: 'loading'}, | ||
| cb, | ||
| 0, | ||
| getState | ||
| ); | ||
| expect(result).to.deep.equal([{id: 'home1', type: 'loading'}]); | ||
| }); | ||
|
|
||
| it('leaves a fully concrete id untouched', () => { | ||
| const result = replacePMC( | ||
| {id: 'home1', type: 'loading'}, | ||
| cb, | ||
| 0, | ||
| getState | ||
| ); | ||
| expect(result).to.deep.equal([{id: 'home1', type: 'loading'}]); | ||
| }); | ||
|
|
||
| it('yields no ids when ALL matches nothing on the current page', () => { | ||
| // Regression test for #3297: navigating to a page that has none of | ||
| // the wildcard components used to throw | ||
| // `state.paths.objs[idKey] is undefined`, and later returned a | ||
| // partial id missing the wildcard key. | ||
| const empty = () => makeState({}); | ||
| const result = replacePMC({id: ['ALL'], type: 'loading'}, cb, 0, empty); | ||
| expect(result).to.deep.equal([]); | ||
| }); | ||
|
|
||
| it('yields no ids when ALLSMALLER matches nothing on the current page', () => { | ||
| const empty = () => makeState({}); | ||
| const result = replacePMC( | ||
| {id: ['ALLSMALLER'], type: 'loading'}, | ||
| cb, | ||
| 0, | ||
| empty | ||
| ); | ||
| expect(result).to.deep.equal([]); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trim to two lines. Something like: "Fix
running=with a pattern-matchingALL/ALLSMALLERid crashing the renderer when none of the matching components are on the current page. Fixes #3297." The navigation example and the resolve-to-empty detail belong in the commit body or.ai/.