Skip to content

Commit e787f8b

Browse files
authored
Creating an IndexStore for Quick Navigation (#889) rdar://134272215
Creating an `IndexStore` for Quick Navigation (#889) rdar://134272215
1 parent 6fc174e commit e787f8b

13 files changed

+1250
-35
lines changed

Diff for: src/components/DocumentationLayout.vue

+7-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
<div class="documentation-layout-aside">
4141
<QuickNavigationModal
4242
v-if="enableQuickNavigation"
43-
:children="slotProps.flatChildren"
43+
:children="quickNavNodes"
4444
:showQuickNavigationModal.sync="showQuickNavigationModal"
4545
:technology="technology ? technology.title : ''"
4646
/>
@@ -83,10 +83,12 @@ import QuickNavigationModal from 'docc-render/components/Navigator/QuickNavigati
8383
import AdjustableSidebarWidth from 'docc-render/components/AdjustableSidebarWidth.vue';
8484
import Navigator from 'docc-render/components/Navigator.vue';
8585
import onPageLoadScrollToFragment from 'docc-render/mixins/onPageLoadScrollToFragment';
86+
import Language from 'docc-render/constants/Language';
8687
import { BreakpointName } from 'docc-render/utils/breakpoints';
8788
import { storage } from 'docc-render/utils/storage';
8889
import { getSetting } from 'docc-render/utils/theme-settings';
8990

91+
import IndexStore from 'docc-render/stores/IndexStore';
9092
import NavigatorDataProvider from 'theme/components/Navigator/NavigatorDataProvider.vue';
9193
import DocumentationNav from 'theme/components/DocumentationTopic/DocumentationNav.vue';
9294

@@ -150,12 +152,16 @@ export default {
150152
sidenavHiddenOnLarge: storage.get(NAVIGATOR_HIDDEN_ON_LARGE_KEY, false),
151153
showQuickNavigationModal: false,
152154
BreakpointName,
155+
indexState: IndexStore.state,
153156
};
154157
},
155158
computed: {
156159
enableQuickNavigation: ({ isTargetIDE }) => (
157160
!isTargetIDE && getSetting(['features', 'docs', 'quickNavigation', 'enable'], true)
158161
),
162+
quickNavNodes({ indexState: { flatChildren = {} }, interfaceLanguage }) {
163+
return flatChildren[interfaceLanguage] ?? (flatChildren[Language.swift.key.url] || []);
164+
},
159165
sidebarProps: ({
160166
sidenavVisibleOnMobile, enableNavigator, sidenavHiddenOnLarge, navigatorFixedWidth,
161167
}) => (

Diff for: src/components/Navigator/NavigatorDataProvider.vue

-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
<script>
1212
import { fetchIndexPathsData } from 'docc-render/utils/data';
1313
import { flattenNestedData } from 'docc-render/utils/navigatorData';
14-
import AppStore from 'docc-render/stores/AppStore';
1514
import Language from 'docc-render/constants/Language';
1615

1716
/**
@@ -91,15 +90,13 @@ export default {
9190
try {
9291
this.isFetching = true;
9392
const {
94-
includedArchiveIdentifiers = [],
9593
interfaceLanguages,
9694
references,
9795
} = await fetchIndexPathsData(
9896
{ slug: this.$route.params.locale || '' },
9997
);
10098
this.navigationIndex = Object.freeze(interfaceLanguages);
10199
this.navigationReferences = Object.freeze(references);
102-
AppStore.setIncludedArchiveIdentifiers(includedArchiveIdentifiers);
103100
} catch (e) {
104101
this.errorFetching = true;
105102
} finally {

Diff for: src/mixins/indexProvider.js

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* This source file is part of the Swift.org open source project
3+
*
4+
* Copyright (c) 2024 Apple Inc. and the Swift project authors
5+
* Licensed under Apache License v2.0 with Runtime Library Exception
6+
*
7+
* See https://swift.org/LICENSE.txt for license information
8+
* See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
import { fetchData } from 'docc-render/utils/data';
11+
import { pathJoin } from 'docc-render/utils/assets';
12+
import { flattenNavigationIndex, extractTechnologyProps } from 'docc-render/utils/navigatorData';
13+
import IndexStore from 'docc-render/stores/IndexStore';
14+
15+
export default {
16+
computed: {
17+
indexDataPath() {
18+
const slug = this.$route?.params?.locale || '';
19+
return pathJoin(['/index/', slug, 'index.json']);
20+
},
21+
},
22+
methods: {
23+
async fetchIndexPathsData() {
24+
return fetchData(this.indexDataPath);
25+
},
26+
async fetchIndexData() {
27+
try {
28+
IndexStore.reset();
29+
const {
30+
includedArchiveIdentifiers = [],
31+
interfaceLanguages,
32+
references = {},
33+
} = await this.fetchIndexPathsData();
34+
IndexStore.setFlatChildren(flattenNavigationIndex(interfaceLanguages));
35+
IndexStore.setTechnologyProps(extractTechnologyProps(interfaceLanguages));
36+
IndexStore.setReferences(references);
37+
IndexStore.setIncludedArchiveIdentifiers(includedArchiveIdentifiers);
38+
} catch (e) {
39+
IndexStore.setErrorFetching(true);
40+
}
41+
},
42+
},
43+
watch: {
44+
indexDataPath: {
45+
handler: 'fetchIndexData',
46+
immediate: true,
47+
},
48+
},
49+
};

Diff for: src/stores/AppStore.js

-5
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,13 @@ export default {
3030
supportsAutoColorScheme,
3131
systemColorScheme: ColorScheme.light,
3232
availableLocales: [],
33-
includedArchiveIdentifiers: [],
3433
},
3534
reset() {
3635
this.state.imageLoadingStrategy = process.env.VUE_APP_TARGET === 'ide'
3736
? ImageLoadingStrategy.eager : ImageLoadingStrategy.lazy;
3837
this.state.preferredColorScheme = Settings.preferredColorScheme || defaultColorScheme;
3938
this.state.supportsAutoColorScheme = supportsAutoColorScheme;
4039
this.state.systemColorScheme = ColorScheme.light;
41-
this.state.includedArchiveIdentifiers = [];
4240
},
4341
setImageLoadingStrategy(strategy) {
4442
this.state.imageLoadingStrategy = strategy;
@@ -61,9 +59,6 @@ export default {
6159
setSystemColorScheme(value) {
6260
this.state.systemColorScheme = value;
6361
},
64-
setIncludedArchiveIdentifiers(value) {
65-
this.state.includedArchiveIdentifiers = value;
66-
},
6762
syncPreferredColorScheme() {
6863
if (!!Settings.preferredColorScheme
6964
&& Settings.preferredColorScheme !== this.state.preferredColorScheme) {

Diff for: src/stores/IndexStore.js

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* This source file is part of the Swift.org open source project
3+
*
4+
* Copyright (c) 2024 Apple Inc. and the Swift project authors
5+
* Licensed under Apache License v2.0 with Runtime Library Exception
6+
*
7+
* See https://swift.org/LICENSE.txt for license information
8+
* See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
11+
export default {
12+
state: {
13+
flatChildren: {},
14+
references: {},
15+
apiChanges: null,
16+
includedArchiveIdentifiers: [],
17+
errorFetching: false,
18+
technologyProps: {},
19+
},
20+
reset() {
21+
this.state.flatChildren = {};
22+
this.state.references = {};
23+
this.state.apiChanges = null;
24+
this.state.includedArchiveIdentifiers = [];
25+
this.state.errorFetching = false;
26+
this.state.technologyProps = {};
27+
},
28+
setFlatChildren(children) {
29+
this.state.flatChildren = children;
30+
},
31+
setReferences(references) {
32+
this.state.references = references;
33+
},
34+
setApiChanges(diff) {
35+
this.state.apiChanges = diff;
36+
},
37+
setIncludedArchiveIdentifiers(includedArchiveIdentifiers) {
38+
this.state.includedArchiveIdentifiers = includedArchiveIdentifiers;
39+
},
40+
setErrorFetching(state) {
41+
this.state.errorFetching = state;
42+
},
43+
setTechnologyProps(technology) {
44+
this.state.technologyProps = technology;
45+
},
46+
};

Diff for: src/utils/navigatorData.js

+27
Original file line numberDiff line numberDiff line change
@@ -177,3 +177,30 @@ export function getSiblings(uid, childrenMap, children) {
177177
if (!item) return [];
178178
return getChildren(item.parent, childrenMap, children);
179179
}
180+
181+
/**
182+
* Flatten data for each language variant
183+
* @return { languageVariant: NavigatorFlatItem[] }
184+
*/
185+
export function flattenNavigationIndex(indexData) {
186+
return Object.entries(indexData).reduce((acc, [language, data]) => {
187+
acc[language] = flattenNestedData(
188+
data[0].children || [], null, 0, data[0].beta,
189+
);
190+
return acc;
191+
}, {});
192+
}
193+
194+
/**
195+
* Extract technology data for each language variant
196+
*/
197+
export function extractTechnologyProps(indexData) {
198+
return Object.entries(indexData).reduce((acc, [language, data]) => {
199+
acc[language] = {
200+
technology: data[0].title,
201+
technologyPath: data[0].path || data[0].url,
202+
isTechnologyBeta: data[0].beta,
203+
};
204+
return acc;
205+
}, {});
206+
}

Diff for: src/views/DocumentationTopic.vue

+2-1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import {
5454
import DocumentationTopic from 'theme/components/DocumentationTopic.vue';
5555
import DocumentationLayout from 'docc-render/components/DocumentationLayout.vue';
5656
import DocumentationTopicStore from 'docc-render/stores/DocumentationTopicStore';
57+
import indexProvider from 'theme/mixins/indexProvider';
5758
import Language from 'docc-render/constants/Language';
5859
import OnThisPageRegistrator from 'docc-render/mixins/onThisPageRegistrator';
5960
import { updateLocale } from 'theme/utils/i18n-utils';
@@ -74,7 +75,7 @@ export default {
7475
Topic: DocumentationTopic,
7576
DocumentationLayout,
7677
},
77-
mixins: [OnThisPageRegistrator, communicationBridgeUtils],
78+
mixins: [OnThisPageRegistrator, communicationBridgeUtils, indexProvider],
7879
props: {
7980
enableMinimized: {
8081
type: Boolean,

Diff for: tests/unit/components/DocumentationLayout.spec.js

+72
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,20 @@ jest.mock('docc-render/utils/scroll-lock');
3030
jest.mock('docc-render/utils/storage');
3131
jest.mock('docc-render/utils/theme-settings');
3232

33+
const swiftChildren = [
34+
'swiftChildrenMock',
35+
];
36+
const objcChildren = [
37+
'objcChildrenMock',
38+
];
39+
jest.mock('docc-render/stores/IndexStore', () => ({
40+
state: {
41+
flatChildren: {
42+
swift: swiftChildren,
43+
},
44+
},
45+
}));
46+
3347
storage.get.mockImplementation((key, value) => value);
3448

3549
const TechnologyWithChildren = {
@@ -273,6 +287,64 @@ describe('DocumentationLayout', () => {
273287
expect(quickNavigationModalComponent.exists()).toBe(false);
274288
});
275289

290+
it('QuickNavigation renders Swift items', async () => {
291+
getSetting.mockReturnValueOnce(true);
292+
wrapper = createWrapper({
293+
stubs: {
294+
...stubs,
295+
Nav: DocumentationNav,
296+
NavBase,
297+
},
298+
});
299+
wrapper.setProps({
300+
enableNavigator: true,
301+
});
302+
await wrapper.vm.$nextTick();
303+
expect(wrapper.find(QuickNavigationModal).props('children')).toEqual(swiftChildren);
304+
});
305+
306+
it('QuickNavigation falls back to swift items, if no objc items', async () => {
307+
getSetting.mockReturnValueOnce(true);
308+
wrapper = createWrapper({
309+
stubs: {
310+
...stubs,
311+
Nav: DocumentationNav,
312+
NavBase,
313+
},
314+
});
315+
wrapper.setProps({
316+
enableNavigator: true,
317+
interfaceLanguage: Language.objectiveC.key.url,
318+
});
319+
await wrapper.vm.$nextTick();
320+
expect(wrapper.find(QuickNavigationModal).props('children')).toEqual(swiftChildren);
321+
});
322+
323+
it('QuickNavigation renders objc items', async () => {
324+
getSetting.mockReturnValueOnce(true);
325+
wrapper = createWrapper({
326+
stubs: {
327+
...stubs,
328+
Nav: DocumentationNav,
329+
NavBase,
330+
},
331+
});
332+
wrapper.setProps({
333+
enableNavigator: true,
334+
interfaceLanguage: Language.objectiveC.key.url,
335+
});
336+
wrapper.setData({
337+
indexState: {
338+
flatChildren: {
339+
[Language.swift.key.url]: swiftChildren,
340+
[Language.objectiveC.key.url]: objcChildren,
341+
},
342+
},
343+
});
344+
await wrapper.vm.$nextTick();
345+
expect(wrapper.find(QuickNavigationModal).props('children')).toEqual(objcChildren);
346+
});
347+
276348
describe('if breakpoint is small', () => {
277349
beforeEach(() => {
278350
wrapper = createWrapper({

Diff for: tests/unit/components/Navigator/NavigatorDataProvider.spec.js

-13
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111
import NavigatorDataProvider from '@/components/Navigator/NavigatorDataProvider.vue';
1212
import { shallowMount } from '@vue/test-utils';
13-
import AppStore from 'docc-render/stores/AppStore';
1413
import Language from 'docc-render/constants/Language';
1514
import { TopicTypes } from '@/constants/TopicTypes';
1615
import { fetchIndexPathsData } from '@/utils/data';
@@ -175,10 +174,6 @@ describe('NavigatorDataProvider', () => {
175174
jest.clearAllMocks();
176175
});
177176

178-
afterEach(() => {
179-
AppStore.reset();
180-
});
181-
182177
it('fetches data when mounting NavigatorDataProvider', async () => {
183178
expect(fetchIndexPathsData).toHaveBeenCalledTimes(0);
184179
createWrapper();
@@ -559,12 +554,4 @@ describe('NavigatorDataProvider', () => {
559554
},
560555
]);
561556
});
562-
563-
it('sets `includedArchiveIdentifiers` state in the app store', async () => {
564-
expect(AppStore.state.includedArchiveIdentifiers).toEqual([]);
565-
fetchIndexPathsData.mockResolvedValue(response);
566-
createWrapper();
567-
await flushPromises();
568-
expect(AppStore.state.includedArchiveIdentifiers).toEqual(includedArchiveIdentifiers);
569-
});
570557
});

0 commit comments

Comments
 (0)