Skip to content

Commit

Permalink
Merge pull request #2095 from sanger/develop
Browse files Browse the repository at this point in the history
[release] Merge Develop into Master
  • Loading branch information
yoldas authored Dec 3, 2024
2 parents c5b6084 + ff8153e commit 5b3b2fe
Show file tree
Hide file tree
Showing 39 changed files with 772 additions and 587 deletions.
2 changes: 1 addition & 1 deletion .release-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.64.2
3.64.3
11 changes: 1 addition & 10 deletions app/frontend/entrypoints/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,7 @@ import '@/javascript/lib/ajax_support.js'
import '@/javascript/lib/array_fill_polyfill.js'

// Currently setting up each component as its own mini vue app.
import '@/javascript/asset-comments/index.js'
import '@/javascript/custom-tagged-plate/index.js'
import '@/javascript/file-list/index.js'
import '@/javascript/labware-custom-metadata/index.js'
import '@/javascript/multi-stamp-tubes/index.js'
import '@/javascript/multi-stamp/index.js'
import '@/javascript/qc-information/index.js'
import '@/javascript/tubes-to-rack/index.js'
import '@/javascript/validate-paired-tubes/index.js'
import '@/javascript/pool-xp-tube-panel/index.js'
import '@/javascript/vue_app.js'

// Load simple javascript files
import '@/javascript/plain-javascript/page-reloader.js'
Expand Down
41 changes: 41 additions & 0 deletions app/frontend/javascript/asset-comments/comment-store-helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import devourApi from '@/javascript/shared/devourApi.js'
import resources from '@/javascript/shared/resources.js'
import axios from 'axios'
import commentStoreFactory from '@/javascript/asset-comments/comment-store.js'

const commentFactoryStores = {}

const createCommentFactory = (props) => {
let commentFactoryStoreForAsset = commentFactoryStores[props.assetId]
if (commentFactoryStoreForAsset && commentFactoryStoreForAsset.commentFactory) {
commentFactoryStoreForAsset.refCount++
return commentFactoryStoreForAsset.commentFactory
}

const sequencescapeApiUrl = props.sequencescapeApi
const sequencescapeApiKey = props.sequencescapeApiKey
const axiosInstance = axios.create({
baseURL: sequencescapeApiUrl,
timeout: 10000,
headers: {
Accept: 'application/vnd.api+json',
'Content-Type': 'application/vnd.api+json',
},
})
const api = devourApi({ apiUrl: sequencescapeApiUrl }, resources, sequencescapeApiKey)
const commentFactory = commentStoreFactory(axiosInstance, api, props.assetId, props.userId)

commentFactoryStores[props.assetId] = { commentFactory, refCount: 1 }
return commentFactory
}

const removeCommentFactory = (assetId) => {
if (commentFactoryStores[assetId]) {
commentFactoryStores[assetId].refCount -= 1
if (commentFactoryStores[assetId].refCount === 0) {
delete commentFactoryStores[assetId]
}
}
}

export { createCommentFactory, removeCommentFactory, commentFactoryStores }
102 changes: 102 additions & 0 deletions app/frontend/javascript/asset-comments/comment-store-helpers.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import {
createCommentFactory,
removeCommentFactory,
commentFactoryStores,
} from '@/javascript/asset-comments/comment-store-helpers.js'
import devourApi from '@/javascript/shared/devourApi.js'
import resources from '@/javascript/shared/resources.js'
import commentStoreFactory from '@/javascript/asset-comments/comment-store.js'
import axios from 'axios'

vi.mock('axios')
vi.mock('@/javascript/shared/devourApi.js')
vi.mock('@/javascript/asset-comments/comment-store.js')

describe('commentFactory', () => {
let mockAxiosInstance
let mockDevourApi
let mockCommentFactory

beforeEach(() => {
mockAxiosInstance = {
create: vi.fn().mockReturnValue({
baseURL: 'http://example.com/api',
timeout: 10000,
headers: {
Accept: 'application/vnd.api+json',
'Content-Type': 'application/vnd.api+json',
},
}),
}
axios.create.mockReturnValue(mockAxiosInstance)

mockDevourApi = vi.fn()
devourApi.mockReturnValue(mockDevourApi)

mockCommentFactory = {}
commentStoreFactory.mockReturnValue(mockCommentFactory)
})

afterEach(() => {
vi.clearAllMocks()
})

it('creates a new commentFactory and stores it', () => {
const props = {
sequencescapeApi: 'http://example.com/api',
sequencescapeApiKey: 'test-api-key',
assetId: '123',
userId: 'user_id',
}

const result = createCommentFactory(props)

expect(axios.create).toHaveBeenCalledWith({
baseURL: props.sequencescapeApi,
timeout: 10000,
headers: {
Accept: 'application/vnd.api+json',
'Content-Type': 'application/vnd.api+json',
},
})
expect(devourApi).toHaveBeenCalledWith({ apiUrl: props.sequencescapeApi }, resources, props.sequencescapeApiKey)
expect(commentStoreFactory).toHaveBeenCalledWith(mockAxiosInstance, mockDevourApi, props.assetId, props.userId)
expect(result).toBe(mockCommentFactory)
expect(commentFactoryStores[props.assetId]).toEqual({ commentFactory: mockCommentFactory, refCount: 1 })
})

it('returns an existing commentFactory and increments the refCount', () => {
const props = {
sequencescapeApi: 'http://example.com/api',
sequencescapeApiKey: 'test-api-key',
assetId: '123',
userId: 'user_id',
}

commentFactoryStores[props.assetId] = { commentFactory: mockCommentFactory, refCount: 1 }

const result = createCommentFactory(props)

expect(result).toBe(mockCommentFactory)
expect(commentFactoryStores[props.assetId].refCount).toBe(2)
expect(commentStoreFactory).not.toHaveBeenCalled()
})

it('removes a commentFactory when refCount drops to zero', () => {
const assetId = '123'
commentFactoryStores[assetId] = { commentFactory: mockCommentFactory, refCount: 1 }

removeCommentFactory(assetId)

expect(commentFactoryStores[assetId]).toBeUndefined()
})

it('decrements the refCount without removing the commentFactory', () => {
const assetId = '123'
commentFactoryStores[assetId] = { commentFactory: mockCommentFactory, refCount: 2 }

removeCommentFactory(assetId)

expect(commentFactoryStores[assetId].refCount).toBe(1)
})
})
3 changes: 2 additions & 1 deletion app/frontend/javascript/asset-comments/comment-store.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Shared object for retrieval of comments from SequenceScape API

import eventBus from '@/javascript/shared/eventBus.js'
const commentStoreFactory = function (axiosInstance, devourApi, assetId, userId) {
return {
comments: undefined,
Expand Down Expand Up @@ -64,7 +65,7 @@ const commentStoreFactory = function (axiosInstance, devourApi, assetId, userId)
})

await this.refreshComments()

eventBus.$emit('update-comments', { comments: this.comments, assetId: assetId })
return true
},
}
Expand Down
6 changes: 6 additions & 0 deletions app/frontend/javascript/asset-comments/comment-store.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ import axios from 'axios'
import MockAdapter from 'axios-mock-adapter'
import mockApi from '@/javascript/test_support/mock_api.js'
import { jsonCollectionFactory } from '@/javascript/test_support/factories.js'
import eventBus from '@/javascript/shared/eventBus.js'

describe('commentStore', () => {
let mockEmit
beforeEach(() => {
mockEmit = vi.spyOn(eventBus, '$emit')
})
const noComments = { data: [] }
const comments = jsonCollectionFactory('comment', [
{
Expand Down Expand Up @@ -77,6 +82,7 @@ describe('commentStore', () => {
await flushPromises()

expect(commentStore.comments.length).toEqual(2)
expect(mockEmit).toHaveBeenCalledWith('update-comments', { comments: commentStore.comments, assetId: '123' })
})

it('posts comments to the sequencescape api', async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,19 @@
// Import the component being tested
import { shallowMount } from '@vue/test-utils'

import AssetComments from './AssetComments.vue'
import {
mountWithCommentFactory,
testCommentFactoryInitAndDestroy,
} from '@/javascript/asset-comments/components/component-test-utils.js'
import eventBus from '@/javascript/shared/eventBus.js'

// Here are some Jasmine 2.0 tests, though you can
// use any test runner / assertion library combo you prefer
describe('AssetComments', () => {
const wrapperFactory = function (comments) {
const parent = {
data() {
return { comments }
},
}
testCommentFactoryInitAndDestroy(AssetComments, [
{ id: 1, text: 'Test comment', user: { login: 'js1', first_name: 'John', last_name: 'Smith' } },
])

return shallowMount(AssetComments, { parentComponent: parent })
}

it('renders a list of comments', () => {
const wrapper = wrapperFactory([
it('renders a list of comments', async () => {
const mockComments = [
{
id: '1234',
title: null,
Expand All @@ -44,7 +40,9 @@ describe('AssetComments', () => {
last_name: 'Smythe',
},
},
])
]
const { wrapper } = mountWithCommentFactory(AssetComments, mockComments)
await wrapper.vm.$nextTick()

expect(wrapper.find('.comments-list').exists()).toBe(true)
expect(wrapper.find('.comments-list').findAll('li').length).toBe(2)
Expand All @@ -57,19 +55,68 @@ describe('AssetComments', () => {
expect(wrapper.find('.comments-list').findAll('li').wrappers[1].text()).toContain('31 August 2017 at 11:18')
})

it('renders a message when there are no comments', () => {
const wrapper = wrapperFactory([])

it('renders a message when there are no comments', async () => {
const { wrapper } = mountWithCommentFactory(AssetComments, [])
await wrapper.vm.$nextTick()
expect(wrapper.find('.comments-list').exists()).toBe(true)
expect(wrapper.find('.comments-list').findAll('li').length).toBe(1)
expect(wrapper.find('.comments-list').findAll('li.no-comment').length).toBe(1)
expect(wrapper.find('.comments-list').find('li').text()).toContain('No comments available')
})

it('renders a spinner when comments are not loaded', () => {
const wrapper = shallowMount(AssetComments)
const { wrapper } = mountWithCommentFactory(AssetComments, null)

expect(wrapper.find('.comments-list').exists()).toBe(true)
expect(wrapper.find('.comments-list').find('.spinner-dark').exists()).toBe(true)
})

it('updates comments when eventBus emits update-comments', async () => {
const { wrapper } = mountWithCommentFactory(AssetComments, [])
await wrapper.vm.$nextTick()
expect(wrapper.find('.comments-list').find('li').text()).toContain('No comments available')
eventBus.$emit('update-comments', {
assetId: '123',
comments: [
{
id: '12345',
title: null,
description: 'This is also a comment',
created_at: '2017-09-30T12:18:16+01:00',
updated_at: '2017-09-30T12:18:16+01:00',
user: {
id: '13',
login: 'js2',
first_name: 'Jane',
last_name: 'Smythe',
},
},
],
})
await wrapper.vm.$nextTick()
expect(wrapper.find('.comments-list').findAll('li').length).toBe(1)
expect(wrapper.find('.comments-list').find('li').text()).toContain('Jane Smythe (js2)')
})
it('does not update comments when eventBus emits update-comments for a different assetId', async () => {
const { wrapper } = mountWithCommentFactory(AssetComments, [])
await wrapper.vm.$nextTick()
expect(wrapper.find('.comments-list').find('li').text()).toContain('No comments available')
eventBus.$emit('update-comments', {
assetId: '345',
comments: [
{
id: 1,
text: 'Test comment',
user: {
id: '13',
login: 'js2',
first_name: 'Jane',
last_name: 'Smythe',
},
},
],
})
await wrapper.vm.$nextTick()
expect(wrapper.find('.comments-list').find('li').text()).toContain('No comments available')
})
})
Loading

0 comments on commit 5b3b2fe

Please sign in to comment.