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));