From e787f8b2b39b60ce95ecc2874eee97690dbe5df5 Mon Sep 17 00:00:00 2001
From: Hanqing Huang <87735557+hqhhuang@users.noreply.github.com>
Date: Mon, 16 Sep 2024 09:59:15 -0700
Subject: [PATCH 1/5] Creating an `IndexStore` for Quick Navigation (#889)
rdar://134272215
Creating an `IndexStore` for Quick Navigation (#889) rdar://134272215
---
src/components/DocumentationLayout.vue | 8 +-
.../Navigator/NavigatorDataProvider.vue | 3 -
src/mixins/indexProvider.js | 49 ++
src/stores/AppStore.js | 5 -
src/stores/IndexStore.js | 46 ++
src/utils/navigatorData.js | 27 +
src/views/DocumentationTopic.vue | 3 +-
.../components/DocumentationLayout.spec.js | 72 +++
.../Navigator/NavigatorDataProvider.spec.js | 13 -
.../__snapshots__/indexProvider.spec.js.snap | 418 ++++++++++++++
tests/unit/mixins/indexProvider.spec.js | 521 ++++++++++++++++++
tests/unit/stores/AppStore.spec.js | 12 -
tests/unit/stores/IndexStore.spec.js | 108 ++++
13 files changed, 1250 insertions(+), 35 deletions(-)
create mode 100644 src/mixins/indexProvider.js
create mode 100644 src/stores/IndexStore.js
create mode 100644 tests/unit/mixins/__snapshots__/indexProvider.spec.js.snap
create mode 100644 tests/unit/mixins/indexProvider.spec.js
create mode 100644 tests/unit/stores/IndexStore.spec.js
diff --git a/src/components/DocumentationLayout.vue b/src/components/DocumentationLayout.vue
index f53dfe20c..65d4da948 100644
--- a/src/components/DocumentationLayout.vue
+++ b/src/components/DocumentationLayout.vue
@@ -40,7 +40,7 @@
@@ -83,10 +83,12 @@ import QuickNavigationModal from 'docc-render/components/Navigator/QuickNavigati
import AdjustableSidebarWidth from 'docc-render/components/AdjustableSidebarWidth.vue';
import Navigator from 'docc-render/components/Navigator.vue';
import onPageLoadScrollToFragment from 'docc-render/mixins/onPageLoadScrollToFragment';
+import Language from 'docc-render/constants/Language';
import { BreakpointName } from 'docc-render/utils/breakpoints';
import { storage } from 'docc-render/utils/storage';
import { getSetting } from 'docc-render/utils/theme-settings';
+import IndexStore from 'docc-render/stores/IndexStore';
import NavigatorDataProvider from 'theme/components/Navigator/NavigatorDataProvider.vue';
import DocumentationNav from 'theme/components/DocumentationTopic/DocumentationNav.vue';
@@ -150,12 +152,16 @@ export default {
sidenavHiddenOnLarge: storage.get(NAVIGATOR_HIDDEN_ON_LARGE_KEY, false),
showQuickNavigationModal: false,
BreakpointName,
+ indexState: IndexStore.state,
};
},
computed: {
enableQuickNavigation: ({ isTargetIDE }) => (
!isTargetIDE && getSetting(['features', 'docs', 'quickNavigation', 'enable'], true)
),
+ quickNavNodes({ indexState: { flatChildren = {} }, interfaceLanguage }) {
+ return flatChildren[interfaceLanguage] ?? (flatChildren[Language.swift.key.url] || []);
+ },
sidebarProps: ({
sidenavVisibleOnMobile, enableNavigator, sidenavHiddenOnLarge, navigatorFixedWidth,
}) => (
diff --git a/src/components/Navigator/NavigatorDataProvider.vue b/src/components/Navigator/NavigatorDataProvider.vue
index 23c5c9d22..26fc9c76a 100644
--- a/src/components/Navigator/NavigatorDataProvider.vue
+++ b/src/components/Navigator/NavigatorDataProvider.vue
@@ -11,7 +11,6 @@
diff --git a/src/components/Navigator/NavigatorDataProvider.vue b/src/components/Navigator/NavigatorDataProvider.vue
deleted file mode 100644
index 26fc9c76a..000000000
--- a/src/components/Navigator/NavigatorDataProvider.vue
+++ /dev/null
@@ -1,125 +0,0 @@
-
-
-
diff --git a/src/mixins/indexDataGetter.js b/src/mixins/indexDataGetter.js
index 11eefa7b4..0d4d9c77d 100644
--- a/src/mixins/indexDataGetter.js
+++ b/src/mixins/indexDataGetter.js
@@ -34,14 +34,12 @@ export default {
apiChanges,
errorFetching,
},
- technologyProps,
}) => ({
flatChildren: indexNodes,
navigatorReferences: references,
apiChanges,
isFetching: !flatChildren && !errorFetching,
errorFetching,
- technologyProps,
}),
},
data() {
diff --git a/src/views/DocumentationTopic.vue b/src/views/DocumentationTopic.vue
index 2731adb08..e33415ab9 100644
--- a/src/views/DocumentationTopic.vue
+++ b/src/views/DocumentationTopic.vue
@@ -52,7 +52,7 @@ import {
shouldFetchDataForRouteUpdate,
} from 'docc-render/utils/data';
import DocumentationTopic from 'theme/components/DocumentationTopic.vue';
-import DocumentationLayout from 'docc-render/components/DocumentationLayout.vue';
+import DocumentationLayout from 'theme/components/DocumentationLayout.vue';
import DocumentationTopicStore from 'docc-render/stores/DocumentationTopicStore';
import indexDataFetcher from 'theme/mixins/indexDataFetcher';
import Language from 'docc-render/constants/Language';
diff --git a/tests/unit/components/DocumentationLayout.spec.js b/tests/unit/components/DocumentationLayout.spec.js
index 30763b2b1..978300d1e 100644
--- a/tests/unit/components/DocumentationLayout.spec.js
+++ b/tests/unit/components/DocumentationLayout.spec.js
@@ -8,14 +8,12 @@
* See https://swift.org/CONTRIBUTORS.txt for Swift project authors
*/
-import * as dataUtils from 'docc-render/utils/data';
import { shallowMount } from '@vue/test-utils';
import DocumentationTopicStore from 'docc-render/stores/DocumentationTopicStore';
import onPageLoadScrollToFragment from 'docc-render/mixins/onPageLoadScrollToFragment';
import DocumentationNav from 'docc-render/components/DocumentationTopic/DocumentationNav.vue';
import NavBase from 'docc-render/components/NavBase.vue';
import AdjustableSidebarWidth from '@/components/AdjustableSidebarWidth.vue';
-import NavigatorDataProvider from '@/components/Navigator/NavigatorDataProvider.vue';
import Language from '@/constants/Language';
import Navigator from '@/components/Navigator.vue';
import { storage } from '@/utils/storage';
@@ -36,29 +34,33 @@ const swiftChildren = [
const objcChildren = [
'objcChildrenMock',
];
+
+const swiftProps = {
+ technology: 'swift',
+ technologyPath: '/documentation/swift',
+ isTechnologyBeta: false,
+};
+
jest.mock('docc-render/stores/IndexStore', () => ({
state: {
flatChildren: {
swift: swiftChildren,
},
+ references: { foo: {} },
+ apiChanges: {
+ interfaceLanguages: {
+ swift: [],
+ },
+ },
+ includedArchiveIdentifiers: ['foo', 'bar'],
+ errorFetching: false,
+ technologyProps: {
+ swift: swiftProps,
+ },
},
}));
storage.get.mockImplementation((key, value) => value);
-
-const TechnologyWithChildren = {
- path: '/documentation/foo',
- children: [],
-};
-
-const navigatorReferences = { foo: {} };
-
-jest.spyOn(dataUtils, 'fetchIndexPathsData').mockResolvedValue({
- interfaceLanguages: {
- [Language.swift.key.url]: [TechnologyWithChildren],
- },
- references: navigatorReferences,
-});
getSetting.mockReturnValue(false);
const {
@@ -120,7 +122,6 @@ const AdjustableSidebarWidthSmallStub = {
const stubs = {
AdjustableSidebarWidth,
- NavigatorDataProvider,
DocumentationLayout,
};
@@ -174,45 +175,25 @@ describe('DocumentationLayout', () => {
enableNavigator: true,
fixedWidth: propsData.navigatorFixedWidth,
});
- const {
- technology,
- parentTopicIdentifiers,
- } = propsData;
- expect(wrapper.find(NavigatorDataProvider).props()).toEqual({
- interfaceLanguage: Language.swift.key.url,
- technologyUrl: technology.url,
- apiChangesVersion: '',
- });
+
// its rendered by default
const navigator = wrapper.find(Navigator);
expect(navigator.exists()).toBe(true);
expect(navigator.props()).toEqual({
- errorFetching: false,
- isFetching: true,
- // assert we are passing the first set of paths always
- parentTopicIdentifiers,
- references,
- scrollLockID: AdjustableSidebarWidth.constants.SCROLL_LOCK_ID,
- // assert we are passing the default technology, if we dont have the children yet
- technology,
- apiChanges: null,
- flatChildren: [],
- navigatorReferences: {},
- renderFilterOnTop: false,
- });
- expect(dataUtils.fetchIndexPathsData).toHaveBeenCalledTimes(1);
- await flushPromises();
- expect(navigator.props()).toEqual({
- errorFetching: false,
isFetching: false,
scrollLockID: AdjustableSidebarWidth.constants.SCROLL_LOCK_ID,
renderFilterOnTop: false,
- parentTopicIdentifiers,
references,
- technology: TechnologyWithChildren,
- apiChanges: null,
- flatChildren: [],
- navigatorReferences,
+ apiChanges: {
+ interfaceLanguages: {
+ swift: [],
+ },
+ },
+ flatChildren: swiftChildren,
+ navigatorReferences: { foo: {} },
+ errorFetching: false,
+ parentTopicIdentifiers: propsData.parentTopicIdentifiers,
+ technologyProps: swiftProps,
});
// assert the nav is in wide format
const nav = wrapper.find(Nav);
@@ -350,7 +331,6 @@ describe('DocumentationLayout', () => {
wrapper = createWrapper({
stubs: {
AdjustableSidebarWidth: AdjustableSidebarWidthSmallStub,
- NavigatorDataProvider,
},
});
});
@@ -389,15 +369,6 @@ describe('DocumentationLayout', () => {
});
});
- it('provides the selected api changes, to the NavigatorDataProvider', () => {
- wrapper.setProps({
- enableNavigator: true,
- selectedAPIChangesVersion: 'latest_major',
- });
- const dataProvider = wrapper.find(NavigatorDataProvider);
- expect(dataProvider.props('apiChangesVersion')).toEqual('latest_major');
- });
-
it('renders the Navigator with data when no reference is found for a top-level item', () => {
const technologies = {
id: 'topic://not-existing',
@@ -417,10 +388,7 @@ describe('DocumentationLayout', () => {
const navigator = wrapper.find(Navigator);
expect(navigator.exists()).toBe(true);
// assert the technology is the last fallback
- expect(navigator.props('technology')).toEqual({
- title: 'FooTechnology',
- url: '/documentation/foo',
- });
+ expect(navigator.props('technologyProps')).toEqual(swiftProps);
});
it('renders the Navigator with data when no reference is found, even when there is a reference data error', () => {
@@ -444,10 +412,7 @@ describe('DocumentationLayout', () => {
const navigator = wrapper.find(Navigator);
expect(navigator.exists()).toBe(true);
// assert the technology is the last fallback
- expect(navigator.props('technology')).toEqual({
- title: 'FooTechnology',
- url: '/documentation/foo',
- });
+ expect(navigator.props('technologyProps')).toEqual(swiftProps);
});
it('renders the Navigator with data when no hierarchy and reference is found for the current page', () => {
@@ -460,7 +425,7 @@ describe('DocumentationLayout', () => {
const navigator = wrapper.find(Navigator);
expect(navigator.exists()).toBe(true);
// assert the technology is the last fallback
- expect(navigator.props('technology')).toEqual(propsData.technology);
+ expect(navigator.props('technologyProps')).toEqual(swiftProps);
});
it('renders without a sidebar', () => {
@@ -490,10 +455,6 @@ describe('DocumentationLayout', () => {
.toEqual(expect.arrayContaining(['topic-wrapper', 'full-width-container']));
});
- it('renders without NavigatorDataProvider', async () => {
- expect(wrapper.find(NavigatorDataProvider).exists()).toBe(false);
- });
-
it('handles the `@close`, on Navigator, for Mobile breakpoints', async () => {
wrapper.setProps({
enableNavigator: true,
diff --git a/tests/unit/components/Navigator.spec.js b/tests/unit/components/Navigator.spec.js
index fbfc42f94..ad5e97335 100644
--- a/tests/unit/components/Navigator.spec.js
+++ b/tests/unit/components/Navigator.spec.js
@@ -19,56 +19,10 @@ jest.mock('docc-render/utils/throttle', () => jest.fn(v => v));
const { LoadingNavigatorCard } = Navigator.components;
-const technology = {
- title: 'FooTechnology',
- children: [
- {
- title: 'Group Marker',
- type: TopicTypes.groupMarker,
- },
- {
- title: 'Child0',
- path: '/foo/child0',
- type: 'article',
- children: [
- {
- title: 'Group Marker, Child 0',
- type: TopicTypes.groupMarker,
- },
- {
- title: 'Child0_GrandChild0',
- path: '/foo/child0/grandchild0',
- type: 'tutorial',
- },
- {
- title: 'Child0_GrandChild1',
- path: '/foo/child0/grandchild1',
- type: 'tutorial',
- children: [{
- title: 'Child0_GrandChild0_GreatGrandChild0',
- path: '/foo/child0/grandchild0/greatgrandchild0',
- type: 'tutorial',
- }],
- },
- {
- title: 'Child0_GrandChild2',
- path: '/foo/child0/grandchild2',
- type: 'tutorial',
- },
- ],
- },
- {
- title: 'Child1',
- path: '/foo/child1/',
- type: 'tutorial',
- children: [{
- title: 'Child1_GrandChild0',
- path: '/foo/child1/grandchild0',
- type: 'method',
- }],
- },
- ],
- path: '/path/to/technology',
+const technologyProps = {
+ technology: 'FooTechnology',
+ technologyPath: '/path/to/technology',
+ isTechnologyBeta: false,
};
const references = {
@@ -83,11 +37,6 @@ const parentTopicIdentifiers = [
'root', 'first', 'second',
];
-const fallbackTechnology = {
- title: 'FallbackTechnology',
- url: '/url/to/technology',
-};
-
const mocks = {
$route: {
path: '/current/path',
@@ -100,7 +49,7 @@ const navigatorReferences = {
const defaultProps = {
parentTopicIdentifiers,
- technology,
+ technologyProps,
references,
scrollLockID: 'foo',
renderFilterOnTop: false,
@@ -141,9 +90,9 @@ describe('Navigator', () => {
// will assert in another test
children: [],
type: TopicTypes.module,
- technology: technology.title,
- technologyPath: technology.path,
- isTechnologyBeta: false,
+ technology: technologyProps.technology,
+ technologyPath: technologyProps.technologyPath,
+ isTechnologyBeta: technologyProps.isTechnologyBeta,
scrollLockID: defaultProps.scrollLockID,
renderFilterOnTop: defaultProps.renderFilterOnTop,
errorFetching: false,
@@ -192,29 +141,6 @@ describe('Navigator', () => {
expect(wrapper.find('[aria-live="polite"]').text()).toBe('navigator.navigator-is navigator.state.ready');
});
- it('falls back to using the `technology.url` for the `technology-path`', () => {
- const wrapper = createWrapper({
- propsData: {
- technology: fallbackTechnology,
- },
- });
- expect(wrapper.find(NavigatorCard).props()).toEqual({
- activePath: [references.first.url, references.second.url, mocks.$route.path],
- // will assert in another test
- children: [],
- type: TopicTypes.module,
- technology: fallbackTechnology.title,
- technologyPath: fallbackTechnology.url,
- isTechnologyBeta: false,
- scrollLockID: defaultProps.scrollLockID,
- renderFilterOnTop: defaultProps.renderFilterOnTop,
- errorFetching: false,
- apiChanges: null,
- navigatorReferences,
- hideAvailableTags: false,
- });
- });
-
it('passes the errorFetching prop', () => {
const wrapper = createWrapper({
propsData: {
diff --git a/tests/unit/components/Navigator/NavigatorDataProvider.spec.js b/tests/unit/components/Navigator/NavigatorDataProvider.spec.js
deleted file mode 100644
index f17394249..000000000
--- a/tests/unit/components/Navigator/NavigatorDataProvider.spec.js
+++ /dev/null
@@ -1,557 +0,0 @@
-/**
- * This source file is part of the Swift.org open source project
- *
- * Copyright (c) 2022 Apple Inc. and the Swift project authors
- * Licensed under Apache License v2.0 with Runtime Library Exception
- *
- * See https://swift.org/LICENSE.txt for license information
- * See https://swift.org/CONTRIBUTORS.txt for Swift project authors
-*/
-
-import NavigatorDataProvider from '@/components/Navigator/NavigatorDataProvider.vue';
-import { shallowMount } from '@vue/test-utils';
-import Language from 'docc-render/constants/Language';
-import { TopicTypes } from '@/constants/TopicTypes';
-import { fetchIndexPathsData } from '@/utils/data';
-import { INDEX_ROOT_KEY } from '@/constants/sidebar';
-import { flushPromises } from '../../../../test-utils';
-
-jest.mock('docc-render/utils/data');
-
-const technologyUrl = '/documentation/foo';
-
-const extendedTechnologies = {
- path: '/documentation/foo',
- children: [
- {
- id: 'Group Marker',
- title: 'Group Marker',
- type: TopicTypes.groupMarker,
- },
- {
- id: 'Child0',
- title: 'Child0',
- path: '/documentation/foo/child0',
- type: 'article',
- children: [
- {
- id: 'Group Marker, Child 0',
- title: 'Group Marker, Child 0',
- type: TopicTypes.groupMarker,
- },
- {
- id: 'Child0_GrandChild0',
- title: 'Child0_GrandChild0',
- path: '/documentation/foo/child0/grandchild0',
- type: 'tutorial',
- },
- {
- id: 'Child0_GrandChild1',
- title: 'Child0_GrandChild1',
- path: '/documentation/foo/child0/grandchild1',
- type: 'tutorial',
- children: [{
- id: 'Child0_GrandChild0_GreatGrandChild0',
- title: 'Child0_GrandChild0_GreatGrandChild0',
- path: '/documentation/foo/child0/grandchild0/greatgrandchild0',
- type: 'tutorial',
- }],
- },
- {
- id: 'Child0_GrandChild2',
- title: 'Child0_GrandChild2',
- path: '/documentation/foo/child0/grandchild2',
- type: 'tutorial',
- },
- ],
- },
- {
- id: 'Child1',
- title: 'Child1',
- path: '/documentation/foo/child1/',
- type: 'tutorial',
- children: [{
- id: 'Child1_GrandChild0',
- title: 'Child1_GrandChild0',
- path: '/documentation/foo/child1/grandchild0',
- type: 'method',
- }],
- },
- ],
-};
-
-const flatChildren = [
- {
- uid: -196255993,
- parent: '',
- index: 0,
- siblingsCount: 3,
- depth: 0,
- childUIDs: [],
- },
- {
- uid: -196255992,
- parent: '',
- index: 1,
- siblingsCount: 3,
- depth: 0,
- childUIDs: [],
- },
- {
- uid: -196255991,
- parent: '',
- index: 2,
- siblingsCount: 3,
- depth: 0,
- childUIDs: [],
- },
-];
-
-const swiftIndexOne = {
- id: 'foo',
- path: technologyUrl,
- children: [1, 2, 3],
-};
-const objectiveCIndexOne = {
- id: 'foo-objc',
- path: technologyUrl,
- children: [1],
-};
-
-const references = {
- foo: { bar: 'bar' },
-};
-
-const includedArchiveIdentifiers = [
- 'foo',
- 'bar',
-];
-
-const response = {
- includedArchiveIdentifiers,
- interfaceLanguages: {
- [Language.swift.key.url]: [
- swiftIndexOne,
- ],
- [Language.objectiveC.key.url]: [
- objectiveCIndexOne,
- ],
- },
- references,
-};
-
-fetchIndexPathsData.mockResolvedValue(response);
-
-const defaultProps = {
- technologyUrl,
-};
-
-let props = {};
-
-const createWrapper = ({ propsData, ...others } = {}) => shallowMount(NavigatorDataProvider, {
- propsData: {
- ...defaultProps,
- ...propsData,
- },
- scopedSlots: {
- default: (p) => {
- props = p;
- return 'Text';
- },
- },
- mocks: {
- $route: {
- params: {
- locale: 'en-US',
- },
- },
- },
- ...others,
-});
-
-describe('NavigatorDataProvider', () => {
- beforeEach(() => {
- jest.clearAllMocks();
- });
-
- it('fetches data when mounting NavigatorDataProvider', async () => {
- expect(fetchIndexPathsData).toHaveBeenCalledTimes(0);
- createWrapper();
- expect(fetchIndexPathsData).toHaveBeenCalledTimes(1);
- expect(props).toHaveProperty('isFetching', true);
- await flushPromises();
- expect(props).toEqual({
- apiChanges: null,
- isFetchingAPIChanges: false,
- isFetching: false,
- technology: swiftIndexOne,
- errorFetching: false,
- flatChildren,
- references,
- });
- });
-
- it('fetches data, even if being passed a none-root technology url', async () => {
- expect(fetchIndexPathsData).toHaveBeenCalledTimes(0);
- createWrapper({
- propsData: {
- technologyUrl: `${technologyUrl}/bar/baz`,
- },
- });
- expect(fetchIndexPathsData).toHaveBeenCalledTimes(1);
- expect(props).toHaveProperty('isFetching', true);
- await flushPromises();
- expect(props).toEqual({
- apiChanges: null,
- isFetchingAPIChanges: false,
- isFetching: false,
- technology: swiftIndexOne,
- errorFetching: false,
- flatChildren,
- references,
- });
- });
-
- it('sets errorFetching to true, when request errored', async () => {
- expect(fetchIndexPathsData).toHaveBeenCalledTimes(0);
- fetchIndexPathsData.mockRejectedValueOnce('Error');
- createWrapper();
- expect(fetchIndexPathsData).toHaveBeenCalledTimes(1);
- expect(props).toHaveProperty('isFetching', true);
- await flushPromises();
- expect(props).toEqual({
- apiChanges: null,
- isFetchingAPIChanges: false,
- isFetching: false,
- technology: undefined,
- errorFetching: true,
- flatChildren: [],
- references: {},
- });
- });
-
- it('returns objc data', async () => {
- expect(fetchIndexPathsData).toHaveBeenCalledTimes(0);
- createWrapper({
- propsData: {
- interfaceLanguage: Language.objectiveC.key.url,
- },
- });
- expect(fetchIndexPathsData).toHaveBeenCalledTimes(1);
- expect(props).toHaveProperty('isFetching', true);
- await flushPromises();
- expect(props).toEqual({
- apiChanges: null,
- isFetchingAPIChanges: false,
- errorFetching: false,
- isFetching: false,
- technology: objectiveCIndexOne,
- flatChildren: [{
- uid: -196255993,
- parent: '',
- index: 0,
- siblingsCount: 1,
- depth: 0,
- childUIDs: [],
- }],
- references,
- });
- });
-
- it('falls back to swift items, if no objc items', async () => {
- expect(fetchIndexPathsData).toHaveBeenCalledTimes(0);
- fetchIndexPathsData.mockResolvedValueOnce({
- interfaceLanguages: {
- [Language.swift.key.url]: response.interfaceLanguages[Language.swift.key.url],
- },
- references,
- });
- createWrapper({
- propsData: {
- interfaceLanguage: Language.objectiveC.key.url,
- },
- });
- expect(fetchIndexPathsData).toHaveBeenCalledTimes(1);
- expect(props).toHaveProperty('isFetching', true);
- await flushPromises();
- expect(props).toEqual({
- apiChanges: null,
- isFetchingAPIChanges: false,
- errorFetching: false,
- isFetching: false,
- technology: swiftIndexOne,
- flatChildren,
- references,
- });
- });
-
- it('returns the first technology, if none matches', async () => {
- createWrapper({
- propsData: {
- technologyUrl: '/documentation/bar',
- },
- });
- await flushPromises();
- expect(props).toEqual({
- apiChanges: null,
- isFetchingAPIChanges: false,
- errorFetching: false,
- isFetching: false,
- technology: swiftIndexOne,
- flatChildren,
- references,
- });
- });
-
- it('returns undefined technology when index response is empty', async () => {
- const emptyResponse = { interfaceLanguages: {} };
- fetchIndexPathsData.mockResolvedValue(emptyResponse);
- createWrapper();
- await flushPromises();
- expect(props).toEqual({
- apiChanges: null,
- isFetchingAPIChanges: false,
- isFetching: false,
- technology: undefined,
- errorFetching: false,
- flatChildren: [],
- references: undefined,
- });
- });
-
- it('counts the amount of deprecated items a groupMarker has', async () => {
- const technologyClone = JSON.parse(JSON.stringify(extendedTechnologies));
- technologyClone.children[1].deprecated = true;
- technologyClone.children[2].deprecated = true;
- technologyClone.children[1].children[1].deprecated = true;
- fetchIndexPathsData.mockResolvedValue({
- interfaceLanguages: {
- [Language.swift.key.url]: [
- technologyClone,
- ],
- },
- });
- createWrapper();
- await flushPromises();
- expect(props.flatChildren[0]).toHaveProperty('deprecatedChildrenCount', 2);
- expect(props.flatChildren).toMatchSnapshot();
- });
-
- it('removes the `beta` flag from children, if the parent is a `beta`', async () => {
- const technologyClone = JSON.parse(JSON.stringify(extendedTechnologies));
- technologyClone.beta = true;
- technologyClone.children[1].beta = true;
- technologyClone.children[1].children[0].beta = true;
- fetchIndexPathsData.mockResolvedValue({
- interfaceLanguages: {
- [Language.swift.key.url]: [
- technologyClone,
- ],
- },
- });
- createWrapper();
- await flushPromises();
- expect(props).toMatchSnapshot();
- });
-
- it('removes the `beta` flag from children, if the parent is a `beta`', async () => {
- const technologyClone = JSON.parse(JSON.stringify(extendedTechnologies));
- technologyClone.children[1].beta = true;
- technologyClone.children[1].children[1].beta = true;
- // case where the direct parent is NOT `Beta`, but an ancestor is
- technologyClone.children[1].children[2].children[0].beta = true;
- // set an end node as beta
- technologyClone.children[2].children[0].beta = true;
- fetchIndexPathsData.mockResolvedValue({
- interfaceLanguages: {
- [Language.swift.key.url]: [
- technologyClone,
- ],
- },
- });
- createWrapper();
- await flushPromises();
- expect(props).toMatchSnapshot();
- });
-
- it('flattens deeply nested children and provides them to the NavigatorCard', async () => {
- fetchIndexPathsData.mockResolvedValue({
- interfaceLanguages: {
- [Language.swift.key.url]: [{
- path: '/documentation/foo',
- children: [
- {
- title: 'Group Marker',
- type: TopicTypes.groupMarker,
- },
- {
- title: 'Child0',
- path: '/foo/child0',
- type: 'article',
- children: [
- {
- title: 'Group Marker, Child 0',
- type: TopicTypes.groupMarker,
- },
- {
- title: 'Child0_GrandChild0',
- path: '/foo/child0/grandchild0',
- type: 'tutorial',
- },
- {
- title: 'Child0_GrandChild1',
- path: '/foo/child0/grandchild1',
- type: 'tutorial',
- children: [{
- title: 'Child0_GrandChild0_GreatGrandChild0',
- path: '/foo/child0/grandchild0/greatgrandchild0',
- type: 'tutorial',
- }],
- },
- {
- title: 'Child0_GrandChild2',
- path: '/foo/child0/grandchild2',
- type: 'tutorial',
- },
- ],
- },
- {
- title: 'Child1',
- path: '/foo/child1/',
- type: 'tutorial',
- children: [{
- title: 'Child1_GrandChild0',
- path: '/foo/child1/grandchild0',
- type: 'method',
- }],
- },
- ],
- }],
- },
- });
- createWrapper();
- await flushPromises();
- expect(props.flatChildren).toEqual([
- {
- childUIDs: [
- 551503844,
- -97593391,
- ],
- deprecatedChildrenCount: 0,
- depth: 0,
- index: 0,
- parent: INDEX_ROOT_KEY,
- siblingsCount: 3,
- title: 'Group Marker',
- type: 'groupMarker',
- uid: -196255993,
- },
- {
- childUIDs: [
- -361407047,
- 1438225895,
- 1439149417,
- 1440072939,
- ],
- depth: 0,
- groupMarkerUID: -196255993,
- index: 1,
- parent: INDEX_ROOT_KEY,
- path: '/foo/child0',
- siblingsCount: 3,
- title: 'Child0',
- type: 'article',
- uid: 551503844,
- },
- {
- childUIDs: [
- 1438225895,
- 1439149417,
- 1440072939,
- ],
- deprecatedChildrenCount: 0,
- depth: 1,
- index: 0,
- parent: 551503844,
- siblingsCount: 4,
- title: 'Group Marker, Child 0',
- type: 'groupMarker',
- uid: -361407047,
- },
- {
- childUIDs: [],
- depth: 1,
- groupMarkerUID: -361407047,
- index: 1,
- parent: 551503844,
- path: '/foo/child0/grandchild0',
- siblingsCount: 4,
- title: 'Child0_GrandChild0',
- type: 'tutorial',
- uid: 1438225895,
- },
- {
- childUIDs: [
- 305326087,
- ],
- depth: 1,
- groupMarkerUID: -361407047,
- index: 2,
- parent: 551503844,
- path: '/foo/child0/grandchild1',
- siblingsCount: 4,
- title: 'Child0_GrandChild1',
- type: 'tutorial',
- uid: 1439149417,
- },
- {
- childUIDs: [],
- depth: 2,
- index: 0,
- parent: 1439149417,
- path: '/foo/child0/grandchild0/greatgrandchild0',
- siblingsCount: 1,
- title: 'Child0_GrandChild0_GreatGrandChild0',
- type: 'tutorial',
- uid: 305326087,
- },
- {
- childUIDs: [],
- depth: 1,
- groupMarkerUID: -361407047,
- index: 3,
- parent: 551503844,
- path: '/foo/child0/grandchild2',
- siblingsCount: 4,
- title: 'Child0_GrandChild2',
- type: 'tutorial',
- uid: 1440072939,
- },
- {
- childUIDs: [
- -827353283,
- ],
- depth: 0,
- groupMarkerUID: -196255993,
- index: 2,
- parent: INDEX_ROOT_KEY,
- path: '/foo/child1/',
- siblingsCount: 3,
- title: 'Child1',
- type: 'tutorial',
- uid: -97593391,
- },
- {
- childUIDs: [],
- depth: 1,
- index: 0,
- parent: -97593391,
- path: '/foo/child1/grandchild0',
- siblingsCount: 1,
- title: 'Child1_GrandChild0',
- type: 'method',
- uid: -827353283,
- },
- ]);
- });
-});
diff --git a/tests/unit/components/Navigator/__snapshots__/NavigatorDataProvider.spec.js.snap b/tests/unit/components/Navigator/__snapshots__/NavigatorDataProvider.spec.js.snap
deleted file mode 100644
index f65dfb359..000000000
--- a/tests/unit/components/Navigator/__snapshots__/NavigatorDataProvider.spec.js.snap
+++ /dev/null
@@ -1,559 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`NavigatorDataProvider counts the amount of deprecated items a groupMarker has 1`] = `
-Array [
- Object {
- "childUIDs": Array [
- 365745369,
- -1561138820,
- ],
- "deprecatedChildrenCount": 2,
- "depth": 0,
- "id": "Group Marker",
- "index": 0,
- "parent": ,
- "siblingsCount": 3,
- "title": "Group Marker",
- "type": "groupMarker",
- "uid": -196255993,
- },
- Object {
- "childUIDs": Array [
- -1893192488,
- -2046366661,
- -2045443139,
- -2044519617,
- ],
- "deprecated": true,
- "depth": 0,
- "groupMarkerUID": -196255993,
- "id": "Child0",
- "index": 1,
- "parent": ,
- "path": "/documentation/foo/child0",
- "siblingsCount": 3,
- "title": "Child0",
- "type": "article",
- "uid": 365745369,
- },
- Object {
- "childUIDs": Array [
- -2046366661,
- -2045443139,
- -2044519617,
- ],
- "deprecatedChildrenCount": 1,
- "depth": 1,
- "id": "Group Marker, Child 0",
- "index": 0,
- "parent": 365745369,
- "siblingsCount": 4,
- "title": "Group Marker, Child 0",
- "type": "groupMarker",
- "uid": -1893192488,
- },
- Object {
- "childUIDs": Array [],
- "deprecated": true,
- "depth": 1,
- "groupMarkerUID": -1893192488,
- "id": "Child0_GrandChild0",
- "index": 1,
- "parent": 365745369,
- "path": "/documentation/foo/child0/grandchild0",
- "siblingsCount": 4,
- "title": "Child0_GrandChild0",
- "type": "tutorial",
- "uid": -2046366661,
- },
- Object {
- "childUIDs": Array [
- 366459535,
- ],
- "depth": 1,
- "groupMarkerUID": -1893192488,
- "id": "Child0_GrandChild1",
- "index": 2,
- "parent": 365745369,
- "path": "/documentation/foo/child0/grandchild1",
- "siblingsCount": 4,
- "title": "Child0_GrandChild1",
- "type": "tutorial",
- "uid": -2045443139,
- },
- Object {
- "childUIDs": Array [],
- "depth": 2,
- "id": "Child0_GrandChild0_GreatGrandChild0",
- "index": 0,
- "parent": -2045443139,
- "path": "/documentation/foo/child0/grandchild0/greatgrandchild0",
- "siblingsCount": 1,
- "title": "Child0_GrandChild0_GreatGrandChild0",
- "type": "tutorial",
- "uid": 366459535,
- },
- Object {
- "childUIDs": Array [],
- "depth": 1,
- "groupMarkerUID": -1893192488,
- "id": "Child0_GrandChild2",
- "index": 3,
- "parent": 365745369,
- "path": "/documentation/foo/child0/grandchild2",
- "siblingsCount": 4,
- "title": "Child0_GrandChild2",
- "type": "tutorial",
- "uid": -2044519617,
- },
- Object {
- "childUIDs": Array [
- 1069111095,
- ],
- "deprecated": true,
- "depth": 0,
- "groupMarkerUID": -196255993,
- "id": "Child1",
- "index": 2,
- "parent": ,
- "path": "/documentation/foo/child1/",
- "siblingsCount": 3,
- "title": "Child1",
- "type": "tutorial",
- "uid": -1561138820,
- },
- Object {
- "childUIDs": Array [],
- "depth": 1,
- "id": "Child1_GrandChild0",
- "index": 0,
- "parent": -1561138820,
- "path": "/documentation/foo/child1/grandchild0",
- "siblingsCount": 1,
- "title": "Child1_GrandChild0",
- "type": "method",
- "uid": 1069111095,
- },
-]
-`;
-
-exports[`NavigatorDataProvider removes the \`beta\` flag from children, if the parent is a \`beta\` 1`] = `
-Object {
- "apiChanges": null,
- "errorFetching": false,
- "flatChildren": Array [
- Object {
- "childUIDs": Array [
- 365745369,
- -1561138820,
- ],
- "deprecatedChildrenCount": 0,
- "depth": 0,
- "id": "Group Marker",
- "index": 0,
- "parent": ,
- "siblingsCount": 3,
- "title": "Group Marker",
- "type": "groupMarker",
- "uid": -196255993,
- },
- Object {
- "beta": false,
- "childUIDs": Array [
- -1893192488,
- -2046366661,
- -2045443139,
- -2044519617,
- ],
- "depth": 0,
- "groupMarkerUID": -196255993,
- "id": "Child0",
- "index": 1,
- "parent": ,
- "path": "/documentation/foo/child0",
- "siblingsCount": 3,
- "title": "Child0",
- "type": "article",
- "uid": 365745369,
- },
- Object {
- "beta": false,
- "childUIDs": Array [
- -2046366661,
- -2045443139,
- -2044519617,
- ],
- "deprecatedChildrenCount": 0,
- "depth": 1,
- "id": "Group Marker, Child 0",
- "index": 0,
- "parent": 365745369,
- "siblingsCount": 4,
- "title": "Group Marker, Child 0",
- "type": "groupMarker",
- "uid": -1893192488,
- },
- Object {
- "childUIDs": Array [],
- "depth": 1,
- "groupMarkerUID": -1893192488,
- "id": "Child0_GrandChild0",
- "index": 1,
- "parent": 365745369,
- "path": "/documentation/foo/child0/grandchild0",
- "siblingsCount": 4,
- "title": "Child0_GrandChild0",
- "type": "tutorial",
- "uid": -2046366661,
- },
- Object {
- "childUIDs": Array [
- 366459535,
- ],
- "depth": 1,
- "groupMarkerUID": -1893192488,
- "id": "Child0_GrandChild1",
- "index": 2,
- "parent": 365745369,
- "path": "/documentation/foo/child0/grandchild1",
- "siblingsCount": 4,
- "title": "Child0_GrandChild1",
- "type": "tutorial",
- "uid": -2045443139,
- },
- Object {
- "childUIDs": Array [],
- "depth": 2,
- "id": "Child0_GrandChild0_GreatGrandChild0",
- "index": 0,
- "parent": -2045443139,
- "path": "/documentation/foo/child0/grandchild0/greatgrandchild0",
- "siblingsCount": 1,
- "title": "Child0_GrandChild0_GreatGrandChild0",
- "type": "tutorial",
- "uid": 366459535,
- },
- Object {
- "childUIDs": Array [],
- "depth": 1,
- "groupMarkerUID": -1893192488,
- "id": "Child0_GrandChild2",
- "index": 3,
- "parent": 365745369,
- "path": "/documentation/foo/child0/grandchild2",
- "siblingsCount": 4,
- "title": "Child0_GrandChild2",
- "type": "tutorial",
- "uid": -2044519617,
- },
- Object {
- "childUIDs": Array [
- 1069111095,
- ],
- "depth": 0,
- "groupMarkerUID": -196255993,
- "id": "Child1",
- "index": 2,
- "parent": ,
- "path": "/documentation/foo/child1/",
- "siblingsCount": 3,
- "title": "Child1",
- "type": "tutorial",
- "uid": -1561138820,
- },
- Object {
- "childUIDs": Array [],
- "depth": 1,
- "id": "Child1_GrandChild0",
- "index": 0,
- "parent": -1561138820,
- "path": "/documentation/foo/child1/grandchild0",
- "siblingsCount": 1,
- "title": "Child1_GrandChild0",
- "type": "method",
- "uid": 1069111095,
- },
- ],
- "isFetching": false,
- "isFetchingAPIChanges": false,
- "references": undefined,
- "technology": Object {
- "beta": true,
- "children": Array [
- Object {
- "id": "Group Marker",
- "title": "Group Marker",
- "type": "groupMarker",
- },
- Object {
- "beta": true,
- "children": Array [
- Object {
- "beta": true,
- "id": "Group Marker, Child 0",
- "title": "Group Marker, Child 0",
- "type": "groupMarker",
- },
- Object {
- "id": "Child0_GrandChild0",
- "path": "/documentation/foo/child0/grandchild0",
- "title": "Child0_GrandChild0",
- "type": "tutorial",
- },
- Object {
- "children": Array [
- Object {
- "id": "Child0_GrandChild0_GreatGrandChild0",
- "path": "/documentation/foo/child0/grandchild0/greatgrandchild0",
- "title": "Child0_GrandChild0_GreatGrandChild0",
- "type": "tutorial",
- },
- ],
- "id": "Child0_GrandChild1",
- "path": "/documentation/foo/child0/grandchild1",
- "title": "Child0_GrandChild1",
- "type": "tutorial",
- },
- Object {
- "id": "Child0_GrandChild2",
- "path": "/documentation/foo/child0/grandchild2",
- "title": "Child0_GrandChild2",
- "type": "tutorial",
- },
- ],
- "id": "Child0",
- "path": "/documentation/foo/child0",
- "title": "Child0",
- "type": "article",
- },
- Object {
- "children": Array [
- Object {
- "id": "Child1_GrandChild0",
- "path": "/documentation/foo/child1/grandchild0",
- "title": "Child1_GrandChild0",
- "type": "method",
- },
- ],
- "id": "Child1",
- "path": "/documentation/foo/child1/",
- "title": "Child1",
- "type": "tutorial",
- },
- ],
- "path": "/documentation/foo",
- },
-}
-`;
-
-exports[`NavigatorDataProvider removes the \`beta\` flag from children, if the parent is a \`beta\` 2`] = `
-Object {
- "apiChanges": null,
- "errorFetching": false,
- "flatChildren": Array [
- Object {
- "childUIDs": Array [
- 365745369,
- -1561138820,
- ],
- "deprecatedChildrenCount": 0,
- "depth": 0,
- "id": "Group Marker",
- "index": 0,
- "parent": ,
- "siblingsCount": 3,
- "title": "Group Marker",
- "type": "groupMarker",
- "uid": -196255993,
- },
- Object {
- "beta": true,
- "childUIDs": Array [
- -1893192488,
- -2046366661,
- -2045443139,
- -2044519617,
- ],
- "depth": 0,
- "groupMarkerUID": -196255993,
- "id": "Child0",
- "index": 1,
- "parent": ,
- "path": "/documentation/foo/child0",
- "siblingsCount": 3,
- "title": "Child0",
- "type": "article",
- "uid": 365745369,
- },
- Object {
- "childUIDs": Array [
- -2046366661,
- -2045443139,
- -2044519617,
- ],
- "deprecatedChildrenCount": 0,
- "depth": 1,
- "id": "Group Marker, Child 0",
- "index": 0,
- "parent": 365745369,
- "siblingsCount": 4,
- "title": "Group Marker, Child 0",
- "type": "groupMarker",
- "uid": -1893192488,
- },
- Object {
- "beta": false,
- "childUIDs": Array [],
- "depth": 1,
- "groupMarkerUID": -1893192488,
- "id": "Child0_GrandChild0",
- "index": 1,
- "parent": 365745369,
- "path": "/documentation/foo/child0/grandchild0",
- "siblingsCount": 4,
- "title": "Child0_GrandChild0",
- "type": "tutorial",
- "uid": -2046366661,
- },
- Object {
- "childUIDs": Array [
- 366459535,
- ],
- "depth": 1,
- "groupMarkerUID": -1893192488,
- "id": "Child0_GrandChild1",
- "index": 2,
- "parent": 365745369,
- "path": "/documentation/foo/child0/grandchild1",
- "siblingsCount": 4,
- "title": "Child0_GrandChild1",
- "type": "tutorial",
- "uid": -2045443139,
- },
- Object {
- "beta": false,
- "childUIDs": Array [],
- "depth": 2,
- "id": "Child0_GrandChild0_GreatGrandChild0",
- "index": 0,
- "parent": -2045443139,
- "path": "/documentation/foo/child0/grandchild0/greatgrandchild0",
- "siblingsCount": 1,
- "title": "Child0_GrandChild0_GreatGrandChild0",
- "type": "tutorial",
- "uid": 366459535,
- },
- Object {
- "childUIDs": Array [],
- "depth": 1,
- "groupMarkerUID": -1893192488,
- "id": "Child0_GrandChild2",
- "index": 3,
- "parent": 365745369,
- "path": "/documentation/foo/child0/grandchild2",
- "siblingsCount": 4,
- "title": "Child0_GrandChild2",
- "type": "tutorial",
- "uid": -2044519617,
- },
- Object {
- "childUIDs": Array [
- 1069111095,
- ],
- "depth": 0,
- "groupMarkerUID": -196255993,
- "id": "Child1",
- "index": 2,
- "parent": ,
- "path": "/documentation/foo/child1/",
- "siblingsCount": 3,
- "title": "Child1",
- "type": "tutorial",
- "uid": -1561138820,
- },
- Object {
- "beta": true,
- "childUIDs": Array [],
- "depth": 1,
- "id": "Child1_GrandChild0",
- "index": 0,
- "parent": -1561138820,
- "path": "/documentation/foo/child1/grandchild0",
- "siblingsCount": 1,
- "title": "Child1_GrandChild0",
- "type": "method",
- "uid": 1069111095,
- },
- ],
- "isFetching": false,
- "isFetchingAPIChanges": false,
- "references": undefined,
- "technology": Object {
- "children": Array [
- Object {
- "id": "Group Marker",
- "title": "Group Marker",
- "type": "groupMarker",
- },
- Object {
- "beta": true,
- "children": Array [
- Object {
- "id": "Group Marker, Child 0",
- "title": "Group Marker, Child 0",
- "type": "groupMarker",
- },
- Object {
- "beta": true,
- "id": "Child0_GrandChild0",
- "path": "/documentation/foo/child0/grandchild0",
- "title": "Child0_GrandChild0",
- "type": "tutorial",
- },
- Object {
- "children": Array [
- Object {
- "beta": true,
- "id": "Child0_GrandChild0_GreatGrandChild0",
- "path": "/documentation/foo/child0/grandchild0/greatgrandchild0",
- "title": "Child0_GrandChild0_GreatGrandChild0",
- "type": "tutorial",
- },
- ],
- "id": "Child0_GrandChild1",
- "path": "/documentation/foo/child0/grandchild1",
- "title": "Child0_GrandChild1",
- "type": "tutorial",
- },
- Object {
- "id": "Child0_GrandChild2",
- "path": "/documentation/foo/child0/grandchild2",
- "title": "Child0_GrandChild2",
- "type": "tutorial",
- },
- ],
- "id": "Child0",
- "path": "/documentation/foo/child0",
- "title": "Child0",
- "type": "article",
- },
- Object {
- "children": Array [
- Object {
- "beta": true,
- "id": "Child1_GrandChild0",
- "path": "/documentation/foo/child1/grandchild0",
- "title": "Child1_GrandChild0",
- "type": "method",
- },
- ],
- "id": "Child1",
- "path": "/documentation/foo/child1/",
- "title": "Child1",
- "type": "tutorial",
- },
- ],
- "path": "/documentation/foo",
- },
-}
-`;
diff --git a/tests/unit/mixins/indexDataGetter.spec.js b/tests/unit/mixins/indexDataGetter.spec.js
index eea6d93d8..bf4be38fe 100644
--- a/tests/unit/mixins/indexDataGetter.spec.js
+++ b/tests/unit/mixins/indexDataGetter.spec.js
@@ -148,6 +148,23 @@ describe('indexDataGetter', () => {
expect(wrapper.vm.technologyProps).toEqual(null);
});
+ it('falls back to using the `technology.url` if no path', () => {
+ const fallbackTechnology = {
+ title: 'FallbackTechnology',
+ url: '/url/to/technology',
+ };
+ IndexStore.state = {
+ ...mockState(),
+ technologyProps: {},
+ };
+ const wrapper = createWrapper();
+ wrapper.setProps({
+ technology: fallbackTechnology,
+ });
+
+ expect(wrapper.vm.technologyProps.technologyPath).toEqual(fallbackTechnology.url);
+ });
+
it('computes the correct fetching status`', () => {
IndexStore.state = {
...mockState(),
From 89da807cdad97469a6860af6ee87379d0739b04a Mon Sep 17 00:00:00 2001
From: Hanqing Huang <87735557+hqhhuang@users.noreply.github.com>
Date: Fri, 18 Oct 2024 14:32:44 -0700
Subject: [PATCH 4/5] Adding slots to allow further navigator customization
(#897) rdar://134362686
Adding slots to allow further navigator customization (#897) rdar://134362686
---
src/components/AdjustableSidebarWidth.vue | 26 ++++++-----
src/components/DocumentationLayout.vue | 10 +++--
src/components/Navigator.vue | 3 ++
src/components/Navigator/NavigatorCard.vue | 3 ++
src/mixins/indexDataGetter.js | 15 ++++++-
src/stores/IndexStore.js | 10 +++++
.../components/AdjustableSidebarWidth.spec.js | 2 +
.../components/DocumentationLayout.spec.js | 6 +--
.../Navigator/NavigatorCard.spec.js | 12 ++++-
tests/unit/mixins/indexDataFetcher.spec.js | 20 ++-------
tests/unit/mixins/indexDataGetter.spec.js | 44 ++++++++++++++++---
tests/unit/stores/IndexStore.spec.js | 16 +++++++
12 files changed, 126 insertions(+), 41 deletions(-)
diff --git a/src/components/AdjustableSidebarWidth.vue b/src/components/AdjustableSidebarWidth.vue
index c97544d7a..a7b46e605 100644
--- a/src/components/AdjustableSidebarWidth.vue
+++ b/src/components/AdjustableSidebarWidth.vue
@@ -157,6 +157,7 @@ export default {
focusTrapInstance: null,
mobileTopOffset: 0,
topOffset: 0,
+ scrollLockContainer: null,
};
},
computed: {
@@ -353,18 +354,23 @@ export default {
* Toggles the scroll lock on/off
*/
async toggleScrollLock(lock) {
- const scrollLockContainer = document.getElementById(this.scrollLockID);
- if (lock) {
- await this.$nextTick();
- scrollLock.lockScroll(scrollLockContainer);
- // lock focus
- this.focusTrapInstance.start();
- // hide sibling elements from VO
- changeElementVOVisibility.hide(this.$refs.aside);
- } else {
- scrollLock.unlockScroll(scrollLockContainer);
+ // if applicable, turn off lock on previous container
+ if (this.scrollLockContainer) {
+ scrollLock.unlockScroll(this.scrollLockContainer);
this.focusTrapInstance.stop();
changeElementVOVisibility.show(this.$refs.aside);
+ this.scrollLockContainer = null;
+ }
+ if (lock) {
+ await this.$nextTick();
+ this.scrollLockContainer = document.getElementById(this.scrollLockID);
+ if (this.scrollLockContainer) {
+ scrollLock.lockScroll(this.scrollLockContainer);
+ // lock focus
+ this.focusTrapInstance.start();
+ // hide sibling elements from VO
+ changeElementVOVisibility.hide(this.$refs.aside);
+ }
}
},
storeTopOffset: throttle(function storeTopOffset() {
diff --git a/src/components/DocumentationLayout.vue b/src/components/DocumentationLayout.vue
index 63abe9b98..53a9c47dd 100644
--- a/src/components/DocumentationLayout.vue
+++ b/src/components/DocumentationLayout.vue
@@ -50,8 +50,9 @@
}"
>
+
+
+
@@ -78,14 +82,14 @@
import { PortalTarget } from 'portal-vue';
import QuickNavigationButton from 'docc-render/components/Navigator/QuickNavigationButton.vue';
import QuickNavigationModal from 'docc-render/components/Navigator/QuickNavigationModal.vue';
-import AdjustableSidebarWidth from 'docc-render/components/AdjustableSidebarWidth.vue';
+import AdjustableSidebarWidth from 'theme/components/AdjustableSidebarWidth.vue';
import Navigator from 'docc-render/components/Navigator.vue';
import onPageLoadScrollToFragment from 'docc-render/mixins/onPageLoadScrollToFragment';
import { BreakpointName } from 'docc-render/utils/breakpoints';
import { storage } from 'docc-render/utils/storage';
import { getSetting } from 'docc-render/utils/theme-settings';
-import indexDataGetter from 'docc-render/mixins/indexDataGetter';
+import indexDataGetter from 'theme/mixins/indexDataGetter';
import DocumentationNav from 'theme/components/DocumentationTopic/DocumentationNav.vue';
const NAVIGATOR_HIDDEN_ON_LARGE_KEY = 'navigator-hidden-large';
diff --git a/src/components/Navigator.vue b/src/components/Navigator.vue
index de3de1c8e..52460b6c7 100644
--- a/src/components/Navigator.vue
+++ b/src/components/Navigator.vue
@@ -27,6 +27,9 @@
@close="$emit('close')"
>
+
+
+
diff --git a/src/components/Navigator/NavigatorCard.vue b/src/components/Navigator/NavigatorCard.vue
index e3a0d2a82..5b4199382 100644
--- a/src/components/Navigator/NavigatorCard.vue
+++ b/src/components/Navigator/NavigatorCard.vue
@@ -395,6 +395,9 @@ export default {
item.updateSize();
}
},
+ technology() {
+ this.clearFilters();
+ },
},
methods: {
setUnlessEqual(property, data) {
diff --git a/src/mixins/indexDataGetter.js b/src/mixins/indexDataGetter.js
index 0d4d9c77d..c692f093b 100644
--- a/src/mixins/indexDataGetter.js
+++ b/src/mixins/indexDataGetter.js
@@ -16,6 +16,19 @@ export default {
if (!flatChildren) return [];
return flatChildren[interfaceLanguage] ?? (flatChildren[Language.swift.key.url] || []);
},
+ apiChanges({
+ indexState: {
+ apiChanges,
+ apiChangesVersion,
+ errorFetchingDiffs,
+ },
+ interfaceLanguage,
+ selectedAPIChangesVersion,
+ }) {
+ if (!apiChanges || errorFetchingDiffs) return undefined;
+ return apiChangesVersion === selectedAPIChangesVersion
+ ? apiChanges[interfaceLanguage] || apiChanges[Language.swift.key.url] : undefined;
+ },
technologyProps({ indexState: { technologyProps }, interfaceLanguage, technology }) {
// Select technology props from fetched index data for the current language, fallback to swift
// If none available, fallback to technology data of the curr page or null
@@ -31,9 +44,9 @@ export default {
indexState: {
flatChildren,
references,
- apiChanges,
errorFetching,
},
+ apiChanges,
}) => ({
flatChildren: indexNodes,
navigatorReferences: references,
diff --git a/src/stores/IndexStore.js b/src/stores/IndexStore.js
index 36e2aeac3..39333424f 100644
--- a/src/stores/IndexStore.js
+++ b/src/stores/IndexStore.js
@@ -13,16 +13,20 @@ export default {
flatChildren: null,
references: {},
apiChanges: null,
+ apiChangesVersion: null,
includedArchiveIdentifiers: [],
errorFetching: false,
+ errorFetchingDiffs: false,
technologyProps: {},
},
reset() {
this.state.flatChildren = null;
this.state.references = {};
this.state.apiChanges = null;
+ this.state.apiChangesVersion = null;
this.state.includedArchiveIdentifiers = [];
this.state.errorFetching = false;
+ this.state.errorFetchingDiffs = false;
this.state.technologyProps = {};
},
setFlatChildren(children) {
@@ -34,12 +38,18 @@ export default {
setApiChanges(diff) {
this.state.apiChanges = diff;
},
+ setApiChangesVersion(version) {
+ this.state.apiChangesVersion = version;
+ },
setIncludedArchiveIdentifiers(includedArchiveIdentifiers) {
this.state.includedArchiveIdentifiers = includedArchiveIdentifiers;
},
setErrorFetching(state) {
this.state.errorFetching = state;
},
+ setErrorFetchingDiffs(state) {
+ this.state.errorFetchingDiffs = state;
+ },
setTechnologyProps(technology) {
this.state.technologyProps = technology;
},
diff --git a/tests/unit/components/AdjustableSidebarWidth.spec.js b/tests/unit/components/AdjustableSidebarWidth.spec.js
index 6e120fef3..9c27a116b 100644
--- a/tests/unit/components/AdjustableSidebarWidth.spec.js
+++ b/tests/unit/components/AdjustableSidebarWidth.spec.js
@@ -219,6 +219,7 @@ describe('AdjustableSidebarWidth', () => {
// called once on mount, once now
expect(boundingClientSpy).toHaveBeenCalledTimes(2);
// assert scroll lock and other helpers initiated
+ expect(wrapper.vm.scrollLockContainer).toBe(scrollLockTarget); // save scroll lock target
expect(scrollLock.lockScroll).toHaveBeenCalledWith(scrollLockTarget);
expect(changeElementVOVisibility.hide).toHaveBeenCalledWith(aside.element);
expect(FocusTrap.mock.results[0].value.start).toHaveBeenCalledTimes(1);
@@ -229,6 +230,7 @@ describe('AdjustableSidebarWidth', () => {
// assert class
expect(aside.classes()).not.toContain('show-on-mobile');
// assert helper status
+ expect(wrapper.vm.scrollLockContainer).toBe(null); // clear scroll lock target
expect(scrollLock.unlockScroll).toHaveBeenCalledWith(scrollLockTarget);
expect(changeElementVOVisibility.show).toHaveBeenCalledWith(aside.element);
expect(FocusTrap.mock.results[0].value.start).toHaveBeenCalledTimes(1);
diff --git a/tests/unit/components/DocumentationLayout.spec.js b/tests/unit/components/DocumentationLayout.spec.js
index 978300d1e..754cc74f5 100644
--- a/tests/unit/components/DocumentationLayout.spec.js
+++ b/tests/unit/components/DocumentationLayout.spec.js
@@ -184,11 +184,7 @@ describe('DocumentationLayout', () => {
scrollLockID: AdjustableSidebarWidth.constants.SCROLL_LOCK_ID,
renderFilterOnTop: false,
references,
- apiChanges: {
- interfaceLanguages: {
- swift: [],
- },
- },
+ apiChanges: null,
flatChildren: swiftChildren,
navigatorReferences: { foo: {} },
errorFetching: false,
diff --git a/tests/unit/components/Navigator/NavigatorCard.spec.js b/tests/unit/components/Navigator/NavigatorCard.spec.js
index 8f817a1d7..f7b8131db 100644
--- a/tests/unit/components/Navigator/NavigatorCard.spec.js
+++ b/tests/unit/components/Navigator/NavigatorCard.spec.js
@@ -195,6 +195,7 @@ const createWrapper = ({ propsData, ...others } = {}) => shallowMount(NavigatorC
});
const clearPersistedStateSpy = jest.spyOn(NavigatorCard.methods, 'clearPersistedState');
+const clearFiltersSpy = jest.spyOn(NavigatorCard.methods, 'clearFilters');
let getChildPositionInScroller;
const DEFAULT_STORED_STATE = {
@@ -1526,7 +1527,7 @@ describe('NavigatorCard', () => {
expect(clearPersistedStateSpy).toHaveBeenCalledTimes(1);
});
- it('does not restore the state, if the technology is different', async () => {
+ it('does not restore the state if the technology is different', async () => {
sessionStorage.get.mockImplementation(() => mergeSessionState({
technology: 'some-other',
nodesToRender: [root0.uid],
@@ -1653,6 +1654,15 @@ describe('NavigatorCard', () => {
expect(wrapper.findAll(NavigatorCardItem)).toHaveLength(4);
});
+ it('clears the filter if the technology is different', async () => {
+ const wrapper = createWrapper();
+ wrapper.setProps({
+ technology: 'newTechnology',
+ });
+ await flushPromises();
+ expect(clearFiltersSpy).toHaveBeenCalledTimes(1);
+ });
+
it('keeps the open state, if there are API changes', async () => {
sessionStorage.get.mockImplementation(() => mergeSessionState({
// simulate we have collapses all, but the top item
diff --git a/tests/unit/mixins/indexDataFetcher.spec.js b/tests/unit/mixins/indexDataFetcher.spec.js
index d75be4363..aefb01d7a 100644
--- a/tests/unit/mixins/indexDataFetcher.spec.js
+++ b/tests/unit/mixins/indexDataFetcher.spec.js
@@ -229,14 +229,7 @@ describe('indexDataFetcher', () => {
createWrapper();
expect(fetchData).toHaveBeenCalledTimes(1);
await flushPromises();
- expect(IndexStore.state).toEqual({
- flatChildren: null,
- references: {},
- apiChanges: null,
- includedArchiveIdentifiers: [],
- errorFetching: true,
- technologyProps: {},
- });
+ expect(IndexStore.state.errorFetching).toEqual(true);
});
it('returns the first technology, if none matches', async () => {
@@ -248,14 +241,7 @@ describe('indexDataFetcher', () => {
},
});
await flushPromises();
- expect(IndexStore.state).toEqual({
- flatChildren,
- references,
- apiChanges: null,
- includedArchiveIdentifiers,
- errorFetching: false,
- technologyProps,
- });
+ expect(IndexStore.state.technologyProps).toEqual(technologyProps);
});
it('returns undefined technology when index response is empty', async () => {
@@ -267,8 +253,10 @@ describe('indexDataFetcher', () => {
flatChildren: {},
references: {},
apiChanges: null,
+ apiChangesVersion: null,
includedArchiveIdentifiers: [],
errorFetching: false,
+ errorFetchingDiffs: false,
technologyProps: {},
});
});
diff --git a/tests/unit/mixins/indexDataGetter.spec.js b/tests/unit/mixins/indexDataGetter.spec.js
index bf4be38fe..19ac2d5ae 100644
--- a/tests/unit/mixins/indexDataGetter.spec.js
+++ b/tests/unit/mixins/indexDataGetter.spec.js
@@ -29,10 +29,12 @@ const references = {
bar: { identifier: 'bar' },
};
-const apiChanges = {
- interfaceLanguages: {
- [Language.swift.key.url]: [],
- },
+const swiftDiff = {
+ '/documentation/swift': 'modified',
+};
+
+const objectiveCDiff = {
+ '/documentation/objc': 'modified',
};
const includedArchiveIdentifiers = ['foo', 'bar'];
@@ -54,9 +56,13 @@ const mockState = () => ({
[Language.swift.key.url]: swiftIndex,
},
references,
- apiChanges,
+ apiChanges: {
+ [Language.swift.key.url]: swiftDiff,
+ },
+ apiChangesVersion: 'version0',
includedArchiveIdentifiers,
errorFetching: false,
+ errorFetchingDiffs: false,
technologyProps: {
[Language.swift.key.url]: swiftProps,
},
@@ -75,6 +81,10 @@ const Component = {
type: Object,
required: false,
},
+ selectedAPIChangesVersion: {
+ type: String,
+ required: false,
+ },
},
};
@@ -87,6 +97,7 @@ const createWrapper = () => shallowMount(Component, {
propsData: {
interfaceLanguage: Language.swift.key.url,
technology,
+ selectedAPIChangesVersion: 'version0',
},
});
@@ -105,6 +116,9 @@ describe('indexDataGetter', () => {
technologyProps: {
[Language.objectiveC.key.url]: objectiveCProps,
},
+ apiChanges: {
+ [Language.objectiveC.key.url]: objectiveCDiff,
+ },
},
});
wrapper.setProps({
@@ -112,6 +126,7 @@ describe('indexDataGetter', () => {
});
expect(wrapper.vm.indexNodes).toEqual(objectiveCIndex);
expect(wrapper.vm.technologyProps).toEqual(objectiveCProps);
+ expect(wrapper.vm.apiChanges).toEqual(objectiveCDiff);
});
it('selects swift variant when language is objc but its data does not exist`', async () => {
@@ -123,6 +138,25 @@ describe('indexDataGetter', () => {
await wrapper.vm.$nextTick();
expect(wrapper.vm.indexNodes).toEqual(swiftIndex);
expect(wrapper.vm.technologyProps).toEqual(swiftProps);
+ expect(wrapper.vm.apiChanges).toEqual(swiftDiff);
+ });
+
+ it('return undefined `apiChanges` if `apiChangesVersion` does not match selected version`', () => {
+ IndexStore.state = {
+ ...mockState(),
+ apiChangesVersion: 'version1',
+ };
+ const wrapper = createWrapper();
+ expect(wrapper.vm.apiChanges).toEqual(undefined);
+ });
+
+ it('return undefined `apiChanges` if `errorFetchingDiffs``', () => {
+ IndexStore.state = {
+ ...mockState(),
+ errorFetchingDiffs: true,
+ };
+ const wrapper = createWrapper();
+ expect(wrapper.vm.apiChanges).toEqual(undefined);
});
it('when no index data is unavailable through IndexStore, fallback to technology provided in prop`', () => {
diff --git a/tests/unit/stores/IndexStore.spec.js b/tests/unit/stores/IndexStore.spec.js
index 365af0f2e..76f6990cd 100644
--- a/tests/unit/stores/IndexStore.spec.js
+++ b/tests/unit/stores/IndexStore.spec.js
@@ -35,6 +35,8 @@ const apiChanges = {
},
};
+const apiChangesVersion = 'version_name';
+
const includedArchiveIdentifiers = ['foo', 'bar'];
const technologyProps = {
@@ -50,8 +52,10 @@ describe('IndexStore', () => {
flatChildren: null,
references: {},
apiChanges: null,
+ apiChangesVersion: null,
includedArchiveIdentifiers: [],
errorFetching: false,
+ errorFetchingDiffs: false,
technologyProps: {},
};
@@ -69,8 +73,10 @@ describe('IndexStore', () => {
IndexStore.state.flatChildren = flatChildren;
IndexStore.state.references = references;
IndexStore.state.apiChanges = apiChanges;
+ IndexStore.state.apiChangesVersion = apiChangesVersion;
IndexStore.state.includedArchiveIdentifiers = includedArchiveIdentifiers;
IndexStore.state.errorFetching = true;
+ IndexStore.state.errorFetchingDiffs = true;
IndexStore.state.technologyProps = technologyProps;
expect(IndexStore.state).not.toEqual(defaultState);
@@ -95,6 +101,11 @@ describe('IndexStore', () => {
expect(IndexStore.state.apiChanges).toEqual(apiChanges);
});
+ it('sets `apiChangesVersion`', () => {
+ IndexStore.setApiChangesVersion(apiChangesVersion);
+ expect(IndexStore.state.apiChangesVersion).toEqual(apiChangesVersion);
+ });
+
it('sets `includedArchiveIdentifiers`', () => {
IndexStore.setIncludedArchiveIdentifiers(includedArchiveIdentifiers);
expect(IndexStore.state.includedArchiveIdentifiers).toEqual(includedArchiveIdentifiers);
@@ -105,6 +116,11 @@ describe('IndexStore', () => {
expect(IndexStore.state.errorFetching).toEqual(true);
});
+ it('sets `errorFetchingDiffs`', () => {
+ IndexStore.setErrorFetchingDiffs(true);
+ expect(IndexStore.state.errorFetchingDiffs).toEqual(true);
+ });
+
it('sets `technologyProps`', () => {
IndexStore.setTechnologyProps(technologyProps);
expect(IndexStore.state.technologyProps).toEqual(technologyProps);
From 3f3b213b47d2cd95ff46381732b81d435d22abc8 Mon Sep 17 00:00:00 2001
From: Hanqing Huang
Date: Fri, 18 Oct 2024 15:44:12 -0700
Subject: [PATCH 5/5] `includedArchiveIdentifier` moved to `IndexStore`
`includedArchiveIdentifier` moved to `IndexStore`
---
src/components/DocumentationTopic.vue | 5 +++--
src/utils/references.js | 4 ++--
tests/unit/components/DocumentationTopic.spec.js | 4 ++--
tests/unit/utils/references.spec.js | 8 ++++----
4 files changed, 11 insertions(+), 10 deletions(-)
diff --git a/src/components/DocumentationTopic.vue b/src/components/DocumentationTopic.vue
index d56486b59..888d8018e 100644
--- a/src/components/DocumentationTopic.vue
+++ b/src/components/DocumentationTopic.vue
@@ -183,6 +183,7 @@ import { normalizeRelativePath } from 'docc-render/utils/assets';
import { last } from 'docc-render/utils/arrays';
import AppStore from 'docc-render/stores/AppStore';
+import IndexStore from 'docc-render/stores/IndexStore';
import Aside from 'docc-render/components/ContentNode/Aside.vue';
import BetaLegalText from 'theme/components/DocumentationTopic/BetaLegalText.vue';
import LanguageSwitcher from 'theme/components/DocumentationTopic/Summary/LanguageSwitcher.vue';
@@ -425,7 +426,7 @@ export default {
},
data() {
return {
- appState: AppStore.state,
+ indexState: IndexStore.state,
topicState: this.store.state,
declListExpanded: false, // Hide all other declarations by default
};
@@ -721,7 +722,7 @@ export default {
this.store.setReferences(this.references);
},
watch: {
- 'appState.includedArchiveIdentifiers': function updateRefs() {
+ 'indexState.includedArchiveIdentifiers': function updateRefs() {
this.store.updateReferences();
},
// update the references in the store, in case they update, but the component is not re-created
diff --git a/src/utils/references.js b/src/utils/references.js
index 1690a74c7..f8d7ed917 100644
--- a/src/utils/references.js
+++ b/src/utils/references.js
@@ -8,7 +8,7 @@
* See https://swift.org/CONTRIBUTORS.txt for Swift project authors
*/
-import AppStore from 'docc-render/stores/AppStore';
+import IndexStore from 'docc-render/stores/IndexStore';
const TopicReferenceTypes = new Set([
'section',
@@ -16,7 +16,7 @@ const TopicReferenceTypes = new Set([
]);
function isFromIncludedArchive(id) {
- const { includedArchiveIdentifiers } = AppStore.state;
+ const { includedArchiveIdentifiers } = IndexStore.state;
// for backwards compatibility purposes, treat all references as being
// from included archives if there is no data for it
diff --git a/tests/unit/components/DocumentationTopic.spec.js b/tests/unit/components/DocumentationTopic.spec.js
index 0dd901c1f..fd9e3a1ea 100644
--- a/tests/unit/components/DocumentationTopic.spec.js
+++ b/tests/unit/components/DocumentationTopic.spec.js
@@ -1171,7 +1171,7 @@ describe('DocumentationTopic', () => {
expect(mockStore.setReferences).toHaveBeenCalledWith(newReferences);
});
- it('calls `store.updateReferences` when `appState.includedArchiveIdentifiers` changes', async () => {
+ it('calls `store.updateReferences` when `indexState.includedArchiveIdentifiers` changes', async () => {
const store = {
state: { references: {} },
reset: jest.fn(),
@@ -1185,7 +1185,7 @@ describe('DocumentationTopic', () => {
expect(store.updateReferences).not.toHaveBeenCalled();
wrapper.setData({
- appState: { includedArchiveIdentifiers: ['Foo', 'Bar'] },
+ indexState: { includedArchiveIdentifiers: ['Foo', 'Bar'] },
});
await wrapper.vm.$nextTick();
expect(store.updateReferences).toHaveBeenCalled();
diff --git a/tests/unit/utils/references.spec.js b/tests/unit/utils/references.spec.js
index a6a2cd7b7..14df91131 100644
--- a/tests/unit/utils/references.spec.js
+++ b/tests/unit/utils/references.spec.js
@@ -8,7 +8,7 @@
* See https://swift.org/CONTRIBUTORS.txt for Swift project authors
*/
-import AppStore from 'docc-render/stores/AppStore';
+import IndexStore from 'docc-render/stores/IndexStore';
import { filterInactiveReferences } from 'docc-render/utils/references';
const aa = {
@@ -52,17 +52,17 @@ const references = {
describe('filterInactiveReferences', () => {
it('does not filter any refs when `includedArchiveIdentifiers` is empty', () => {
- AppStore.setIncludedArchiveIdentifiers([]);
+ IndexStore.setIncludedArchiveIdentifiers([]);
expect(filterInactiveReferences(references)).toEqual(references);
});
it('does not filter any refs when `includedArchiveIdentifiers` includes all ref archives', () => {
- AppStore.setIncludedArchiveIdentifiers(['A', 'B', 'BB']);
+ IndexStore.setIncludedArchiveIdentifiers(['A', 'B', 'BB']);
expect(filterInactiveReferences(references)).toEqual(references);
});
it('removes `url` from non-external refs that aren\'t part of included archive', () => {
- AppStore.setIncludedArchiveIdentifiers(['B']);
+ IndexStore.setIncludedArchiveIdentifiers(['B']);
const filteredRefs = filterInactiveReferences(references);
expect(Object.keys(filteredRefs)).toEqual(Object.keys(references));