Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

Move useLoadMore functionality in the Load More component #1238

Merged
merged 8 commits into from
May 2, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions src/components/VAllResultsGrid/VAllResultsGrid.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,7 @@
</div>
</div>

<VLoadMore
v-if="canLoadMore && !fetchState.isFinished"
class="mt-4"
:is-fetching="resultsLoading"
data-testid="load-more"
@onLoadMore="onLoadMore"
/>
<VLoadMore class="mt-4" />
</div>
</template>

Expand Down
16 changes: 6 additions & 10 deletions src/components/VImageGrid/VImageGrid.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,7 @@
{{ fetchState.fetchingError }}
</h5>
<footer class="pt-4">
<VLoadMore
v-if="canLoadMore && !fetchState.isFinished"
:is-fetching="fetchState.isFetching"
data-testid="load-more"
@onLoadMore="onLoadMore"
/>
<VLoadMore />
</footer>
</section>
</template>
Expand Down Expand Up @@ -66,13 +61,14 @@ export default {
</script>

<style scoped>
.image-grid:after {
/**
@screen md {
.image-grid:after {
/**
* This keeps the last item in the results from expanding to fill
* all avaliable space, which can result in a final row with a
* all available space, which can result in a final row with a
* single, 100% wide image.
*/
@screen md {

content: '';
flex-grow: 999999999;
}
Expand Down
37 changes: 24 additions & 13 deletions src/components/VLoadMore.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<template>
<VButton
v-show="canLoadMore"
size="large"
variant="full"
type="button"
:disabled="isFetching"
data-testid="load-more"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary or could we test just using the label? Generally it's preferable to avoid introducing test id's if possible, in this case a getByText should be okay, I think.

@click="onLoadMore"
>
{{ buttonLabel }}
Expand All @@ -12,33 +13,43 @@
<script lang="ts">
import { computed, defineComponent, useContext } from '@nuxtjs/composition-api'

import { useMediaStore } from '~/stores/media'
import { useSearchStore } from '~/stores/search'

import VButton from '~/components/VButton.vue'

export default defineComponent({
name: 'VLoadMore',
components: {
VButton,
},
props: {
isFetching: {
type: Boolean,
default: true,
},
},
setup(_, { emit }) {
setup() {
const { i18n } = useContext()
const mediaStore = useMediaStore()
const searchStore = useSearchStore()

const buttonLabel = computed(() => {
return i18n.t('browse-page.load')
})
const canLoadMore = computed(
() =>
searchStore.searchTerm !== '' &&
mediaStore.fetchState.canFetch &&
mediaStore.resultCount > 0
)
const onLoadMore = async () => {
if (!canLoadMore.value) return

const onLoadMore = () => {
emit('onLoadMore')
await mediaStore.fetchMedia({
shouldPersistMedia: true,
})
}
const isFetching = computed(() => mediaStore.fetchState.isFetching)

const buttonLabel = computed(() => i18n.t('browse-page.load'))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that it was already this way, but why does this need to be a computed? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it doesn't 😆 I think I wrote it that way when I was only starting to learn the way Composition API worked.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to putting this directly on the template 🙂


return {
buttonLabel,
isFetching,
onLoadMore,
canLoadMore,
}
},
})
Expand Down
31 changes: 27 additions & 4 deletions src/composables/use-fetch-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,33 @@ const canFetchStatuses: Status[] = [statuses.IDLE, statuses.SUCCESS]

/* Composable */

export const useFetchState = (initialState = statuses.IDLE) => {
const fetchStatus: Ref<Status> = ref(initialState)
const fetchError: Ref<string | null> = ref(null)
const isFinished: Ref<boolean> = ref(false)
export const useFetchState = (state?: FetchState | undefined) => {
const initialState: {
status: Status
fetchError: null | string
isFinished: boolean
} = {
status: statuses.IDLE,
fetchError: null,
isFinished: false,
}
if (state) {
if (state.isFetching) {
initialState.status = statuses.FETCHING
} else if (state.fetchingError) {
initialState.status = statuses.ERROR
initialState.fetchError = state.fetchingError
} else if (state.hasStarted) {
initialState.status = statuses.SUCCESS
}
if (state.isFinished) {
initialState.isFinished = true
}
}

const fetchStatus: Ref<Status> = ref(initialState.status)
const fetchError: Ref<string | null> = ref(initialState.fetchError)
const isFinished: Ref<boolean> = ref(initialState.isFinished)

watch(fetchStatus, () => {
if (nonErrorStatuses.includes(fetchStatus.value)) {
Expand Down
7 changes: 1 addition & 6 deletions src/pages/search/audio.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,7 @@
:size="audioTrackSize"
layout="row"
/>
<VLoadMore
v-if="canLoadMore && !fetchState.isFinished"
:is-fetching="fetchState.isFetching"
data-testid="load-more"
@onLoadMore="onLoadMore"
/>
<VLoadMore />
</section>
</template>

Expand Down
45 changes: 19 additions & 26 deletions test/unit/specs/components/image-grid.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { render, screen } from '@testing-library/vue'
import VueI18n from 'vue-i18n'
import { createPinia, PiniaVuePlugin } from 'pinia'
import { createLocalVue } from '@vue/test-utils'

import messages from '~/locales/en.json'

Expand All @@ -17,43 +19,34 @@ const propsData = {
{ id: 'i2', url: 'http://localhost:8080/i2.jpg', title: 'image2' },
{ id: 'i3', url: 'http://localhost:8080/i3.svg', title: 'image3' },
],
canLoadMore: false,
fetchState: {
isFetching: false,
fetchingError: null,
},
}
const options = {
props: propsData,
stubs: ['NuxtLink', 'VLicense'],
mocks: {
$nuxt: {
context: {
i18n,
},
},
},
}

describe('VImageGrid', () => {
it('renders images without load more button if canLoadMore is false', () => {
options.props.canLoadMore = false
render(VImageGrid, options)
expect(screen.queryAllByRole('img').length).toEqual(propsData.images.length)
expect(screen.queryAllByRole('figure').length).toEqual(
propsData.images.length
)
expect(screen.queryByTestId('load-more')).not.toBeInTheDocument()
let localVue
let pinia
let options
beforeEach(() => {
localVue = createLocalVue()
localVue.use(PiniaVuePlugin)
pinia = createPinia()
options = {
localVue,
pinia,
props: propsData,
stubs: ['NuxtLink', 'VLicense'],
mocks: { $nuxt: { context: { i18n } } },
}
})

it('renders images and load more button if canLoadMore is true', async () => {
options.props.canLoadMore = true
it('renders images without load more button', () => {
render(VImageGrid, options)
expect(screen.queryAllByRole('img').length).toEqual(propsData.images.length)
expect(screen.queryAllByRole('figure').length).toEqual(
propsData.images.length
)
const loadMoreButton = screen.queryByTestId('load-more')
expect(loadMoreButton).toBeVisible()
expect(loadMoreButton).toHaveTextContent(messages['browse-page'].load)
expect(screen.queryByTestId('load-more')).not.toBeVisible()
Comment on lines +44 to +50
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test file now misses the other side of it, when the load more button does show.

Also I think there are three branches to test for when load more shows or not, ideally we'd test those in a VLoadMore specific test file:

        searchStore.searchTerm !== '' &&
        mediaStore.fetchState.canFetch &&
        mediaStore.resultCount > 0

Each of those is a distinct branch we'd want to test 😕

Then the image-grid and audio-grid tests can just confirm that the load more component is actually rendered (whether the button displays or not is now irrelevant to the grid as it's handled by the button itself, thank goodness!)

})
})
19 changes: 16 additions & 3 deletions test/unit/specs/components/related-images.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import Vuex from 'vuex'
import VueI18n from 'vue-i18n'
import { createLocalVue } from '@vue/test-utils'
import { render, screen } from '@testing-library/vue'
import { createPinia, PiniaVuePlugin } from 'pinia'

import messages from '~/locales/en.json'

import VRelatedImages from '~/components/VImageDetails/VRelatedImages.vue'

Expand All @@ -14,29 +17,39 @@ const serviceMock = jest.fn(() =>
})
)
const failedMock = jest.fn(() => Promise.reject('No result'))

const i18n = new VueI18n({
locale: 'en',
fallbackLocale: 'en',
messages: { en: messages },
})
const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(VueI18n)
localVue.use(PiniaVuePlugin)
// without nbFetching property on $nuxt, Nuxt's `fetch` hook throws an error:
// [Vue warn]: Error in beforeMount hook (Promise/async):
// "TypeError: Cannot read property 'nbFetching' of undefined"
localVue.prototype.$nuxt = {
nbFetching: 0,
context: i18n,
}

describe('RelatedImage', () => {
let props = null
let options = null
let props
let options
let pinia
beforeEach(() => {
pinia = createPinia()
props = {
imageId: 'foo',
service: { getRelatedMedia: serviceMock },
}
options = {
localVue,
pinia,
propsData: props,
stubs: ['VLicense', 'NuxtLink'],
mocks: { $nuxt: { context: { i18n } } },
}
})
it('should render an image grid', async () => {
Expand Down