diff --git a/.nvmrc b/.nvmrc index 80a9956e1..bb8c76c68 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v20.16.0 +v22.11.0 diff --git a/.release-version b/.release-version index deedb06e0..9b6c27198 100644 --- a/.release-version +++ b/.release-version @@ -1 +1 @@ -3.63.0 +3.64.0 diff --git a/Gemfile b/Gemfile index 4fbf055d1..da5cc3652 100644 --- a/Gemfile +++ b/Gemfile @@ -45,7 +45,7 @@ group :test do gem 'launchy' # Used by capybara for eg. save_and_open_screenshot gem 'rails-controller-testing' gem 'rspec-json_expectations' - gem 'rspec-rails', '6.1.3' + gem 'rspec-rails' gem 'selenium-webdriver', '~> 4.1', require: false gem 'simplecov', require: false gem 'simplecov-lcov', require: false diff --git a/Gemfile.lock b/Gemfile.lock index d6c0e887d..1a4c0abd5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -28,35 +28,35 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (7.1.4) - actionpack (= 7.1.4) - activesupport (= 7.1.4) + actioncable (7.1.5) + actionpack (= 7.1.5) + activesupport (= 7.1.5) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (7.1.4) - actionpack (= 7.1.4) - activejob (= 7.1.4) - activerecord (= 7.1.4) - activestorage (= 7.1.4) - activesupport (= 7.1.4) + actionmailbox (7.1.5) + actionpack (= 7.1.5) + activejob (= 7.1.5) + activerecord (= 7.1.5) + activestorage (= 7.1.5) + activesupport (= 7.1.5) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.1.4) - actionpack (= 7.1.4) - actionview (= 7.1.4) - activejob (= 7.1.4) - activesupport (= 7.1.4) + actionmailer (7.1.5) + actionpack (= 7.1.5) + actionview (= 7.1.5) + activejob (= 7.1.5) + activesupport (= 7.1.5) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp rails-dom-testing (~> 2.2) - actionpack (7.1.4) - actionview (= 7.1.4) - activesupport (= 7.1.4) + actionpack (7.1.5) + actionview (= 7.1.5) + activesupport (= 7.1.5) nokogiri (>= 1.8.5) racc rack (>= 2.2.4) @@ -64,48 +64,52 @@ GEM rack-test (>= 0.6.3) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - actiontext (7.1.4) - actionpack (= 7.1.4) - activerecord (= 7.1.4) - activestorage (= 7.1.4) - activesupport (= 7.1.4) + actiontext (7.1.5) + actionpack (= 7.1.5) + activerecord (= 7.1.5) + activestorage (= 7.1.5) + activesupport (= 7.1.5) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.1.4) - activesupport (= 7.1.4) + actionview (7.1.5) + activesupport (= 7.1.5) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - activejob (7.1.4) - activesupport (= 7.1.4) + activejob (7.1.5) + activesupport (= 7.1.5) globalid (>= 0.3.6) - activemodel (7.1.4) - activesupport (= 7.1.4) - activerecord (7.1.4) - activemodel (= 7.1.4) - activesupport (= 7.1.4) + activemodel (7.1.5) + activesupport (= 7.1.5) + activerecord (7.1.5) + activemodel (= 7.1.5) + activesupport (= 7.1.5) timeout (>= 0.4.0) - activestorage (7.1.4) - actionpack (= 7.1.4) - activejob (= 7.1.4) - activerecord (= 7.1.4) - activesupport (= 7.1.4) + activestorage (7.1.5) + actionpack (= 7.1.5) + activejob (= 7.1.5) + activerecord (= 7.1.5) + activesupport (= 7.1.5) marcel (~> 1.0) - activesupport (7.1.4) + activesupport (7.1.5) base64 + benchmark (>= 0.3) bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) connection_pool (>= 2.2.5) drb i18n (>= 1.6, < 2) + logger (>= 1.4.2) minitest (>= 5.1) mutex_m + securerandom (>= 0.3) tzinfo (~> 2.0) addressable (2.8.7) public_suffix (>= 2.0.2, < 7.0) ast (2.4.2) base64 (0.2.0) + benchmark (0.3.0) bigdecimal (3.1.8) bindex (0.8.1) bootsnap (1.18.4) @@ -132,7 +136,7 @@ GEM bigdecimal rexml crass (1.0.6) - date (3.3.4) + date (3.4.0) diff-lcs (1.5.1) docile (1.4.0) drb (2.2.1) @@ -206,8 +210,8 @@ GEM listen (3.9.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) - logger (1.6.0) - loofah (2.22.0) + logger (1.6.1) + loofah (2.23.1) crass (~> 1.0.2) nokogiri (>= 1.12.0) lumberjack (1.2.10) @@ -227,7 +231,7 @@ GEM multipart-post (2.4.0) mutex_m (0.2.0) nenv (0.3.0) - net-imap (0.4.14) + net-imap (0.5.0) date net-protocol net-pop (0.1.2) @@ -236,14 +240,14 @@ GEM timeout net-smtp (0.5.0) net-protocol - nio4r (2.7.3) + nio4r (2.7.4) nokogiri (1.16.7) mini_portile2 (~> 2.8.2) racc (~> 1.4) notiffany (0.1.3) nenv (~> 0.1) shellany (~> 0.0) - oj (3.16.5) + oj (3.16.7) bigdecimal (>= 3.0) ostruct (>= 0.2) ostruct (0.6.0) @@ -273,23 +277,22 @@ GEM rack (>= 3.0.0) rack-test (2.1.0) rack (>= 1.3) - rackup (2.1.0) + rackup (2.2.0) rack (>= 3) - webrick (~> 1.8) - rails (7.1.4) - actioncable (= 7.1.4) - actionmailbox (= 7.1.4) - actionmailer (= 7.1.4) - actionpack (= 7.1.4) - actiontext (= 7.1.4) - actionview (= 7.1.4) - activejob (= 7.1.4) - activemodel (= 7.1.4) - activerecord (= 7.1.4) - activestorage (= 7.1.4) - activesupport (= 7.1.4) + rails (7.1.5) + actioncable (= 7.1.5) + actionmailbox (= 7.1.5) + actionmailer (= 7.1.5) + actionpack (= 7.1.5) + actiontext (= 7.1.5) + actionview (= 7.1.5) + activejob (= 7.1.5) + activemodel (= 7.1.5) + activerecord (= 7.1.5) + activestorage (= 7.1.5) + activesupport (= 7.1.5) bundler (>= 1.15.0) - railties (= 7.1.4) + railties (= 7.1.5) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1) @@ -301,9 +304,9 @@ GEM rails-html-sanitizer (1.6.0) loofah (~> 2.21) nokogiri (~> 1.14) - railties (7.1.4) - actionpack (= 7.1.4) - activesupport (= 7.1.4) + railties (7.1.5) + actionpack (= 7.1.5) + activesupport (= 7.1.5) irb rackup (>= 1.0.0) rake (>= 12.2) @@ -321,8 +324,7 @@ GEM regexp_parser (2.9.2) reline (0.5.10) io-console (~> 0.5) - rexml (3.3.6) - strscan + rexml (3.3.9) rspec (3.13.0) rspec-core (~> 3.13.0) rspec-expectations (~> 3.13.0) @@ -336,7 +338,7 @@ GEM rspec-mocks (3.13.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-rails (6.1.3) + rspec-rails (6.1.4) actionpack (>= 6.1) activesupport (>= 6.1) railties (>= 6.1) @@ -370,7 +372,8 @@ GEM ruby-units (4.0.3) ruby2_keywords (0.0.5) rubyzip (2.3.2) - selenium-webdriver (4.23.0) + securerandom (0.3.1) + selenium-webdriver (4.26.0) base64 (~> 0.2) logger (~> 1.4) rexml (~> 3.2, >= 3.2.5) @@ -392,7 +395,6 @@ GEM sprint_client (0.1.0) state_machines (0.6.0) stringio (3.1.1) - strscan (3.1.0) syntax_tree (6.2.0) prettier_print (>= 1.2.0) syntax_tree-haml (4.0.3) @@ -427,7 +429,6 @@ GEM addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - webrick (1.8.2) websocket (1.2.11) websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) @@ -435,7 +436,7 @@ GEM xpath (3.2.0) nokogiri (~> 1.8) yard (0.9.36) - zeitwerk (2.6.18) + zeitwerk (2.7.1) PLATFORMS ruby @@ -461,7 +462,7 @@ DEPENDENCIES rails-controller-testing rake rspec-json_expectations - rspec-rails (= 6.1.3) + rspec-rails rubocop rubocop-performance rubocop-rails diff --git a/app/frontend/entrypoints/application.js b/app/frontend/entrypoints/application.js index e4c83293f..ffd059b48 100644 --- a/app/frontend/entrypoints/application.js +++ b/app/frontend/entrypoints/application.js @@ -63,6 +63,7 @@ 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' // Load simple javascript files import '@/javascript/plain-javascript/page-reloader.js' diff --git a/app/frontend/javascript/icons/ErrorIcon.vue b/app/frontend/javascript/icons/ErrorIcon.vue new file mode 100644 index 000000000..0cf41035c --- /dev/null +++ b/app/frontend/javascript/icons/ErrorIcon.vue @@ -0,0 +1,19 @@ + + + diff --git a/app/frontend/javascript/icons/ReadyIcon.vue b/app/frontend/javascript/icons/ReadyIcon.vue new file mode 100644 index 000000000..e69bc747c --- /dev/null +++ b/app/frontend/javascript/icons/ReadyIcon.vue @@ -0,0 +1,19 @@ + + + diff --git a/app/frontend/javascript/icons/SuccessIcon.vue b/app/frontend/javascript/icons/SuccessIcon.vue new file mode 100644 index 000000000..5542d526e --- /dev/null +++ b/app/frontend/javascript/icons/SuccessIcon.vue @@ -0,0 +1,19 @@ + + + diff --git a/app/frontend/javascript/icons/TubeIcon.vue b/app/frontend/javascript/icons/TubeIcon.vue new file mode 100644 index 000000000..afd116364 --- /dev/null +++ b/app/frontend/javascript/icons/TubeIcon.vue @@ -0,0 +1,19 @@ + + + diff --git a/app/frontend/javascript/icons/TubeSearchIcon.vue b/app/frontend/javascript/icons/TubeSearchIcon.vue new file mode 100644 index 000000000..3282b8d8b --- /dev/null +++ b/app/frontend/javascript/icons/TubeSearchIcon.vue @@ -0,0 +1,19 @@ + + + diff --git a/app/frontend/javascript/pool-xp-tube-panel/components/PoolXPTubeSubmitPanel.spec.js b/app/frontend/javascript/pool-xp-tube-panel/components/PoolXPTubeSubmitPanel.spec.js new file mode 100644 index 000000000..fc77b27ab --- /dev/null +++ b/app/frontend/javascript/pool-xp-tube-panel/components/PoolXPTubeSubmitPanel.spec.js @@ -0,0 +1,674 @@ +import { mount, createLocalVue } from '@vue/test-utils' +import PoolXPTubeSubmitPanel from './PoolXPTubeSubmitPanel.vue' +import BootstrapVue from 'bootstrap-vue' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import flushPromises from 'flush-promises' +import ReadyIcon from '../../icons/ReadyIcon.vue' +import TubeSearchIcon from '../../icons/TubeSearchIcon.vue' +import SuccessIcon from '../../icons/SuccessIcon.vue' +import ErrorIcon from '../../icons/ErrorIcon.vue' +import TubeIcon from '../../icons/TubeIcon.vue' + +const localVue = createLocalVue() +localVue.use(BootstrapVue) + +// Default props +const defaultProps = { + barcode: '12345', + userId: 'user123', + sequencescapeApiUrl: 'http://example.com/api', + tractionServiceUrl: 'http://traction.example.com', + tractionUIUrl: 'http://traction-ui.example.com', +} + +// Helper function to create the wrapper with the given state and props +const createWrapper = (state = 'checking_tube_status', props = { ...defaultProps }) => { + return mount(PoolXPTubeSubmitPanel, { + localVue, + propsData: { + ...props, + }, + data() { + return { + state, + } + }, + }) +} + +// Helper function to mock fetch responses sequentially +const mockFetch = (responses) => { + const fetchMock = vi.spyOn(global, 'fetch') + responses.forEach((response) => { + fetchMock.mockImplementationOnce(() => + Promise.resolve({ + ok: response.ok, + json: () => Promise.resolve(response.data), + }), + ) + }) + return fetchMock +} + +//Response objects +const emptyResponse = { ok: true, data: { data: [] } } // Initial successful response +const failedResponse = { ok: false, data: { error: 'API call failed' } } // Subsequent failure response +const successResponse = { ok: true, data: { data: [{ id: '1' }] } } // Subsequent success response + +const mockTubeFoundResponse = () => { + vi.spyOn(global, 'fetch').mockImplementation(() => + Promise.resolve({ + ok: true, + json: () => Promise.resolve({ ...successResponse.data }), + }), + ) +} +const mockTubeCheckFailureResponse = () => { + vi.spyOn(global, 'fetch').mockImplementation(() => + Promise.resolve({ + ok: false, + json: () => Promise.resolve({ ...failedResponse.data }), + }), + ) +} + +const mockNoTubeFoundResponse = () => { + vi.spyOn(global, 'fetch').mockImplementation(() => + Promise.resolve({ + ok: true, + json: () => Promise.resolve({ ...emptyResponse.data }), + }), + ) +} + +// Helper function to check if the component displays the correct state as per the given state value +const verifyComponentState = (wrapper, state) => { + const exportButton = wrapper.find('#pool_xp_tube_export_button') + const statusLabel = wrapper.find('#pool_xp_tube_export_status') + const spinner = wrapper.find('#progress_spinner') + const statusIcon = wrapper.find('#status_icon') + switch (state) { + case 'ready_to_export': { + expect(exportButton.exists()).toBe(true) + expect(statusLabel.exists()).toBe(true) + expect(spinner.isVisible()).toBe(false) + expect(statusIcon.isVisible()).toBe(true) + expect(exportButton.attributes('disabled')).toBeUndefined() + expect(exportButton.classes()).toContain('btn-success') + expect(statusLabel.classes()).toContain('text-success') + expect(statusLabel.text()).toBe('Ready to export') + expect(exportButton.text()).toBe('Export') + const iconComponent = statusIcon.findComponent(ReadyIcon) + expect(iconComponent.exists()).toBe(true) + expect(iconComponent.props().color).toBe('green') + break + } + + case 'checking_tube_status': { + expect(exportButton.exists()).toBe(true) + expect(statusLabel.exists()).toBe(true) + expect(spinner.isVisible()).toBe(true) + expect(statusIcon.isVisible()).toBe(true) + expect(exportButton.attributes('disabled')).toBe('disabled') + expect(exportButton.classes()).toContain('btn-success') + expect(statusLabel.classes()).toContain('text-black') + expect(statusLabel.text()).toBe('Checking tube is in Traction') + expect(exportButton.text()).toBe('Please wait') + const iconComponent = statusIcon.findComponent(TubeSearchIcon) + expect(iconComponent.exists()).toBe(true) + expect(iconComponent.props().color).toContain('black') + break + } + + case 'tube_exists': { + expect(exportButton.exists()).toBe(true) + expect(statusLabel.exists()).toBe(true) + expect(spinner.isVisible()).toBe(false) + expect(statusIcon.isVisible()).toBe(true) + expect(exportButton.attributes('disabled')).toBeUndefined() + expect(exportButton.classes()).toContain('btn-primary') + expect(statusLabel.classes()).toContain('text-success') + expect(statusLabel.text()).toBe('Tube already exported to Traction') + expect(exportButton.text()).toBe('Open Traction') + const iconComponent = statusIcon.findComponent(SuccessIcon) + expect(iconComponent.exists()).toBe(true) + expect(iconComponent.props().color).toContain('green') + break + } + + case 'exporting': { + expect(exportButton.exists()).toBe(true) + expect(statusLabel.exists()).toBe(true) + expect(spinner.isVisible()).toBe(true) + expect(statusIcon.isVisible()).toBe(true) + expect(exportButton.attributes('disabled')).toBe('disabled') + expect(exportButton.classes()).toContain('btn-success') + expect(statusLabel.classes()).toContain('text-black') + expect(statusLabel.text()).toBe('Tube is being exported to Traction') + expect(exportButton.text()).toBe('Please wait') + const iconComponent = statusIcon.findComponent(TubeIcon) + expect(iconComponent.exists()).toBe(true) + expect(iconComponent.props().color).toContain('black') + break + } + + case 'tube_export_success': { + expect(exportButton.exists()).toBe(true) + expect(statusLabel.exists()).toBe(true) + expect(spinner.isVisible()).toBe(false) + expect(statusIcon.isVisible()).toBe(true) + expect(exportButton.attributes('disabled')).toBeUndefined() + expect(exportButton.classes()).toContain('btn-primary') + expect(statusLabel.classes()).toContain('text-success') + expect(statusLabel.text()).toBe('Tube has been exported to Traction') + expect(exportButton.text()).toBe('Open Traction') + const iconComponent = statusIcon.findComponent(SuccessIcon) + expect(iconComponent.exists()).toBe(true) + expect(iconComponent.props().color).toContain('green') + break + } + + case 'failure_export_tube': { + expect(exportButton.exists()).toBe(true) + expect(statusLabel.exists()).toBe(true) + expect(spinner.isVisible()).toBe(false) + expect(statusIcon.isVisible()).toBe(true) + expect(exportButton.attributes('disabled')).toBeUndefined() + expect(exportButton.classes()).toContain('btn-danger') + expect(statusLabel.classes()).toContain('text-danger') + expect(statusLabel.text()).toBe('The tube export to Traction failed. Try again') + expect(exportButton.text()).toBe('Try again') + const iconComponent = statusIcon.findComponent(ErrorIcon) + expect(iconComponent.exists()).toBe(true) + expect(iconComponent.props().color).toContain('red') + break + } + + case 'failure_tube_check': { + expect(exportButton.exists()).toBe(true) + expect(statusLabel.exists()).toBe(true) + expect(spinner.isVisible()).toBe(false) + expect(statusIcon.isVisible()).toBe(true) + expect(exportButton.attributes('disabled')).toBeUndefined() + expect(exportButton.classes()).toContain('btn-danger') + expect(statusLabel.classes()).toContain('text-danger') + expect(statusLabel.text()).toBe('The export cannot be verified. Refresh to try again') + expect(exportButton.text()).toBe('Refresh') + const iconComponent = statusIcon.findComponent(ErrorIcon) + expect(iconComponent.exists()).toBe(true) + expect(iconComponent.props().color).toContain('red') + break + } + + case 'failure_tube_check_export': { + expect(exportButton.exists()).toBe(true) + expect(statusLabel.exists()).toBe(true) + expect(spinner.isVisible()).toBe(false) + expect(statusIcon.isVisible()).toBe(true) + expect(exportButton.attributes('disabled')).toBeUndefined() + expect(exportButton.classes()).toContain('btn-danger') + expect(statusLabel.classes()).toContain('text-danger') + expect(statusLabel.text()).toBe('The export cannot be verified. Try again') + expect(exportButton.text()).toBe('Try again') + const iconComponent = statusIcon.findComponent(ErrorIcon) + expect(iconComponent.exists()).toBe(true) + expect(iconComponent.props().color).toContain('red') + break + } + + case 'invalid_props': { + expect(exportButton.exists()).toBe(true) + expect(statusLabel.exists()).toBe(true) + expect(spinner.isVisible()).toBe(false) + expect(statusIcon.isVisible()).toBe(true) + expect(exportButton.attributes('disabled')).toBe('disabled') + expect(exportButton.classes()).toContain('btn-danger') + expect(statusLabel.classes()).toContain('text-danger') + expect(statusLabel.text()).toBe('Required props are missing') + expect(exportButton.text()).toBe('Export') + const iconComponent = statusIcon.findComponent(ErrorIcon) + expect(iconComponent.exists()).toBe(true) + expect(iconComponent.props().color).toContain('red') + break + } + } +} + +describe('PoolXPTubeSubmitPanel', () => { + let wrapper + + beforeEach(() => { + mockNoTubeFoundResponse() + vi.spyOn(console, 'log').mockImplementation(() => {}) + vi.spyOn(window, 'open').mockImplementation(() => {}) + }) + + it('renders the component', () => { + wrapper = createWrapper() + expect(wrapper.exists()).toBe(true) + }) + + it.each([ + 'ready_to_export', + 'checking_tube_status', + 'tube_exists', + 'exporting', + 'tube_export_success', + 'failure_export_tube', + 'failure_tube_check', + 'failure_tube_check_export', + 'invalid_props', + ])('displays the correct status based on %s state', (stateValue) => { + const wrapper = createWrapper(stateValue) + verifyComponentState(wrapper, stateValue) + }) + + describe('methods', () => { + describe('initialiseStartState', () => { + let wrapper + beforeEach(async () => { + wrapper = createWrapper() + // Mock the sleep function to resolve immediately + wrapper.vm.sleep = vi.fn().mockImplementation(() => Promise.resolve()) + await flushPromises() + }) + it('transitions to TUBE_ALREADY_EXPORTED state when tube is found', async () => { + // Mock the fetch response to indicate a tube is found + mockTubeFoundResponse() + + // Call initialiseStartState + await wrapper.vm.initialiseStartState() + await flushPromises() + + // Check the state after initialiseStartState + expect(wrapper.vm.state).toBe('tube_exists') + }) + + it('transitions to FAILURE_TUBE_CHECK state when service error occurs', async () => { + // Mock the fetch response to indicate a service error + mockTubeCheckFailureResponse() + + // Call initialiseStartState + await wrapper.vm.initialiseStartState() + await flushPromises() + + // Check the state after initialiseStartState + expect(wrapper.vm.state).toBe('failure_tube_check') + }) + + it('transitions to READY_TO_EXPORT state when no tube is found', async () => { + // Mock the fetch response to indicate no tube is found + mockNoTubeFoundResponse() + + // Call initialiseStartState + await wrapper.vm.initialiseStartState() + await flushPromises() + + // Check the state after initialiseStartState + expect(wrapper.vm.state).toBe('ready_to_export') + }) + }) + + describe('checkTubeInTraction', () => { + let wrapper + beforeEach(async () => { + wrapper = createWrapper() + // Mock the sleep function to resolve immediately + wrapper.vm.sleep = vi.fn().mockImplementation(() => Promise.resolve()) + await flushPromises() + }) + + it('returns "found" when tube is found', async () => { + // Mock the fetch response to indicate the tube is found + mockTubeFoundResponse() + const result = await wrapper.vm.checkTubeInTraction() + expect(result).toBe('found') + }) + + it('returns "not_found" when no tube is found', async () => { + // Mock the fetch response to indicate no tube is found + mockNoTubeFoundResponse() + const result = await wrapper.vm.checkTubeInTraction() + expect(result).toBe('not_found') + }) + + it('returns "service_error" when response is not ok', async () => { + // Mock the fetch response to indicate a service error + mockTubeCheckFailureResponse() + const result = await wrapper.vm.checkTubeInTraction() + expect(result).toBe('service_error') + }) + + it('returns "service_error" when an exception is thrown', async () => { + // Mock the fetch to throw an error + vi.spyOn(global, 'fetch').mockImplementationOnce(() => Promise.reject(new Error('Network error'))) + + const result = await wrapper.vm.checkTubeInTraction() + expect(result).toBe('service_error') + }) + }) + + describe('checkTubeStatusWithRetries', () => { + let wrapper + beforeEach(async () => { + wrapper = createWrapper() + wrapper.vm.sleep = vi.fn().mockImplementation(() => Promise.resolve()) + await flushPromises() + }) + it('calls checkTubeStatusWithRetries once when tube is found on first try', async () => { + // Mock the fetch response to indicate the tube is found on the first try + vi.spyOn(wrapper.vm, 'checkTubeInTraction').mockImplementationOnce(() => Promise.resolve('found')) + + const result = await wrapper.vm.checkTubeStatusWithRetries() + expect(result).toBe('found') + expect(wrapper.vm.checkTubeInTraction).toHaveBeenCalledTimes(1) + }) + + it('calls checkTubeStatusWithRetries multiple times when tube is found after retries', async () => { + // Mock the fetch response to indicate the tube is found after some retries + vi.spyOn(wrapper.vm, 'checkTubeInTraction') + .mockImplementationOnce(() => Promise.resolve('not_found')) + .mockImplementationOnce(() => Promise.resolve('not_found')) + .mockImplementationOnce(() => Promise.resolve('found')) + + const result = await wrapper.vm.checkTubeStatusWithRetries(5, 1000) + expect(result).toBe('found') + expect(wrapper.vm.checkTubeInTraction).toHaveBeenCalledTimes(3) + }) + + it('calls checkTubeStatusWithRetries the maximum number of retries when tube is not found', async () => { + // Mock the fetch response to indicate no tube is found after all retries + vi.spyOn(wrapper.vm, 'checkTubeInTraction').mockImplementation(() => Promise.resolve('not_found')) + + const result = await wrapper.vm.checkTubeStatusWithRetries(3, 1000) + expect(result).toBe('not_found') + expect(wrapper.vm.checkTubeInTraction).toHaveBeenCalledTimes(3) + }) + + it('calls checkTubeStatusWithRetries once when service error occurs', async () => { + // Mock the fetch response to indicate a service error + vi.spyOn(wrapper.vm, 'checkTubeInTraction').mockImplementation(() => Promise.resolve('service_error')) + + const result = await wrapper.vm.checkTubeStatusWithRetries(3, 1000) + expect(result).toBe('service_error') + expect(wrapper.vm.checkTubeInTraction).toHaveBeenCalledTimes(3) + }) + }) + }) + + describe('on Mount', () => { + it('calls checkTubeInTraction on mount', async () => { + vi.spyOn(PoolXPTubeSubmitPanel.methods, 'checkTubeInTraction') + wrapper = createWrapper() + expect(PoolXPTubeSubmitPanel.methods.checkTubeInTraction).toHaveBeenCalled() + }) + + it('handles invalid props correctly on mount', async () => { + wrapper = createWrapper('checking_tube_status', { + barcode: '', + userId: '', + sequencescapeApiUrl: '', + tractionServiceUrl: '', + tractionUIUrl: 'http://traction-ui.example.com', + }) + await flushPromises() + expect(wrapper.vm.state).toBe('invalid_props') + verifyComponentState(wrapper, 'invalid_props') + }) + + it('handles fetching tube success from Traction correctly on mount', async () => { + mockTubeFoundResponse() + wrapper = createWrapper() + await flushPromises() + expect(global.fetch).toBeCalledTimes(1) + expect(global.fetch).toHaveBeenCalledWith(wrapper.vm.tractionTubeCheckUrl) + + expect(wrapper.vm.state).toBe('tube_exists') + verifyComponentState(wrapper, 'tube_exists') + }) + it('handles fetching no tubes from Traction correctly on mount', async () => { + mockNoTubeFoundResponse() + wrapper = createWrapper() + wrapper.vm.sleep = vi.fn().mockImplementation(() => Promise.resolve()) + await flushPromises() + expect(wrapper.vm.state).toBe('ready_to_export') + verifyComponentState(wrapper, 'ready_to_export') + }) + + it('handles fetching tube failure from Traction correctly on mount', async () => { + mockTubeCheckFailureResponse() + wrapper = createWrapper() + wrapper.vm.sleep = vi.fn().mockImplementation(() => Promise.resolve()) + await flushPromises() + expect(wrapper.vm.state).toBe('failure_tube_check') + verifyComponentState(wrapper, 'failure_tube_check') + }) + }) + + describe('Export action', () => { + describe('When tube is not already exported and component is in ready_to_export state', () => { + let originalSleep, wrapper + beforeEach(async () => { + mockNoTubeFoundResponse() + wrapper = createWrapper() + originalSleep = wrapper.vm.sleep + // Mock the sleep function to resolve immediately + wrapper.vm.sleep = vi.fn().mockImplementation(() => Promise.resolve()) + await flushPromises() + }) + it('dispalys the correct state', () => { + expect(wrapper.vm.state).toBe('ready_to_export') + verifyComponentState(wrapper, 'ready_to_export') + }) + it('immediately transitions to exporting state when export button is clicked', async () => { + wrapper.vm.sleep = originalSleep + await wrapper.find('#pool_xp_tube_export_button').trigger('click') + await flushPromises() + expect(wrapper.vm.state).toBe('exporting') + }) + it('transitions state correctly when export is successful', async () => { + mockTubeFoundResponse() + await wrapper.find('#pool_xp_tube_export_button').trigger('click') + await flushPromises() + expect(wrapper.vm.state).toBe('tube_export_success') + verifyComponentState(wrapper, 'tube_export_success') + + await wrapper.find('#pool_xp_tube_export_button').trigger('click') + await flushPromises() + expect(window.open).toHaveBeenCalledWith(wrapper.vm.tractionTubeOpenUrl, '_blank') + }) + it('transitions state correctly when no tube exists and export fails', async () => { + mockTubeCheckFailureResponse() + await wrapper.find('#pool_xp_tube_export_button').trigger('click') + await flushPromises() + expect(wrapper.vm.state).toBe('failure_export_tube') + verifyComponentState(wrapper, 'failure_export_tube') + }) + it('transitions states correctly when no tube exists and tube checking using traction api fails', async () => { + mockFetch([ + emptyResponse, // Initial empty response + failedResponse, // Subsequent failure response + ]) + await wrapper.find('#pool_xp_tube_export_button').trigger('click') + await flushPromises() + expect(wrapper.vm.state).toBe('failure_tube_check_export') + verifyComponentState(wrapper, 'failure_tube_check_export') + + mockNoTubeFoundResponse() + await wrapper.find('#pool_xp_tube_export_button').trigger('click') + await flushPromises() + expect(wrapper.vm.state).toBe('failure_tube_check_export') + verifyComponentState(wrapper, 'failure_tube_check_export') + }) + it('transitions states correctly when no tube exists and tube checking using traction api succeeds', async () => { + mockFetch([ + emptyResponse, // Initial empty response + failedResponse, // Subsequent success response + ]) + await wrapper.find('#pool_xp_tube_export_button').trigger('click') + await flushPromises() + expect(wrapper.vm.state).toBe('failure_tube_check_export') + verifyComponentState(wrapper, 'failure_tube_check_export') + + mockTubeFoundResponse() + await wrapper.find('#pool_xp_tube_export_button').trigger('click') + await flushPromises() + expect(wrapper.vm.state).toBe('tube_export_success') + verifyComponentState(wrapper, 'tube_export_success') + }) + }) + describe('When tube is already exported and component is in tube_exists state', () => { + let wrapper + beforeEach(async () => { + mockTubeFoundResponse() + wrapper = createWrapper() + await flushPromises() + }) + it('dispalys the correct state', () => { + expect(wrapper.vm.state).toBe('tube_exists') + verifyComponentState(wrapper, 'tube_exists') + }) + it('opens Traction in a new tab when open traction button is clicked', async () => { + await wrapper.find('#pool_xp_tube_export_button').trigger('click') + await flushPromises() + expect(window.open).toHaveBeenCalledWith(wrapper.vm.tractionTubeOpenUrl, '_blank') + }) + }) + + describe('when traction api to check tube fails on mount and component is in failure_tube_check state', () => { + let wrapper + beforeEach(async () => { + mockTubeCheckFailureResponse() + wrapper = createWrapper() + // Mock the sleep function to resolve immediately + wrapper.vm.sleep = vi.fn().mockImplementation(() => Promise.resolve()) + await flushPromises() + }) + it('displays the correct state', () => { + expect(wrapper.vm.state).toBe('failure_tube_check') + verifyComponentState(wrapper, 'failure_tube_check') + }) + it('remains in failure_tube_check state if response is again failure when refresh button is clicked', async () => { + await wrapper.find('#pool_xp_tube_export_button').trigger('click') + await flushPromises() + expect(wrapper.vm.state).toBe('failure_tube_check') + verifyComponentState(wrapper, 'failure_tube_check') + }) + + it('transitions to ready_to_export state when no existing tubes are found and the refresh button is clicked', async () => { + mockNoTubeFoundResponse() + await wrapper.find('#pool_xp_tube_export_button').trigger('click') + await flushPromises() + expect(wrapper.vm.state).toBe('ready_to_export') + verifyComponentState(wrapper, 'ready_to_export') + }) + it('transitions to tube_exists state when an existing tube is found when refresh button is clicked', async () => { + mockTubeFoundResponse() + await wrapper.find('#pool_xp_tube_export_button').trigger('click') + await flushPromises() + expect(wrapper.vm.state).toBe('tube_exists') + verifyComponentState(wrapper, 'tube_exists') + }) + }) + + describe('when export api fails and the component is in failure_export_tube state', () => { + let wrapper, originalSleep + beforeEach(async () => { + mockNoTubeFoundResponse() + wrapper = createWrapper() + originalSleep = wrapper.vm.sleep + // Mock the sleep function to resolve immediately + wrapper.vm.sleep = vi.fn().mockImplementation(() => Promise.resolve()) + await flushPromises() + mockTubeCheckFailureResponse() + await wrapper.find('#pool_xp_tube_export_button').trigger('click') + await flushPromises() + }) + it('displays the correct state', () => { + expect(wrapper.vm.state).toBe('failure_export_tube') + verifyComponentState(wrapper, 'failure_export_tube') + }) + it('immediately transitions to exporting state when export button is clicked', async () => { + wrapper.vm.sleep = originalSleep + mockNoTubeFoundResponse() + await wrapper.find('#pool_xp_tube_export_button').trigger('click') + await flushPromises() + expect(wrapper.vm.state).toBe('exporting') + verifyComponentState(wrapper, 'exporting') + }) + it('remains in failure_export_tube state if response is failure when retry button is clicked', async () => { + await wrapper.find('#pool_xp_tube_export_button').trigger('click') + await flushPromises() + expect(wrapper.vm.state).toBe('failure_export_tube') + verifyComponentState(wrapper, 'failure_export_tube') + }) + it('transitions to tube_export_success state if response is success when retry button is clicked', async () => { + mockTubeFoundResponse() + await wrapper.find('#pool_xp_tube_export_button').trigger('click') + await flushPromises() + expect(wrapper.vm.state).toBe('tube_export_success') + verifyComponentState(wrapper, 'tube_export_success') + }) + it('transitions to failure_tube_check state if response is tube check api fails when retry button is clicked', async () => { + mockFetch([ + emptyResponse, // Initial successful response + failedResponse, // Subsequent failure response + ]) + await wrapper.find('#pool_xp_tube_export_button').trigger('click') + await flushPromises() + expect(wrapper.vm.state).toBe('failure_tube_check_export') + verifyComponentState(wrapper, 'failure_tube_check_export') + }) + }) + + describe('when the traction api to check tube fails after export and the component is in failure_tube_check_export state', () => { + let wrapper, originalSleep + beforeEach(async () => { + mockNoTubeFoundResponse() + wrapper = createWrapper() + originalSleep = wrapper.vm.sleep + // Mock the sleep function to resolve immediately + wrapper.vm.sleep = vi.fn().mockImplementation(() => Promise.resolve()) + await flushPromises() + mockTubeCheckFailureResponse() + await wrapper.find('#pool_xp_tube_export_button').trigger('click') + await flushPromises() + mockFetch([ + emptyResponse, // Initial successful response + failedResponse, // Subsequent failure response + ]) + await wrapper.find('#pool_xp_tube_export_button').trigger('click') + await flushPromises() + }) + it('displays the correct state', () => { + expect(wrapper.vm.state).toBe('failure_tube_check_export') + verifyComponentState(wrapper, 'failure_tube_check_export') + }) + it('immediately transitions to exporting state when retry button is clicked', async () => { + wrapper.vm.sleep = originalSleep + mockNoTubeFoundResponse() + await wrapper.find('#pool_xp_tube_export_button').trigger('click') + await flushPromises() + expect(wrapper.vm.state).toBe('exporting') + verifyComponentState(wrapper, 'exporting') + }) + it('transitions to tube_export_success state if response is success when retry button is clicked', async () => { + mockTubeFoundResponse() + await wrapper.find('#pool_xp_tube_export_button').trigger('click') + await flushPromises() + expect(wrapper.vm.state).toBe('tube_export_success') + verifyComponentState(wrapper, 'tube_export_success') + }) + it('remains in failure_tube_check_export state if response is failure when retry button is clicked', async () => { + mockFetch([ + emptyResponse, // Initial successful response + failedResponse, // Subsequent failure response + ]) + await wrapper.find('#pool_xp_tube_export_button').trigger('click') + await flushPromises() + expect(wrapper.vm.state).toBe('failure_tube_check_export') + verifyComponentState(wrapper, 'failure_tube_check_export') + }) + }) + }) +}) diff --git a/app/frontend/javascript/pool-xp-tube-panel/components/PoolXPTubeSubmitPanel.vue b/app/frontend/javascript/pool-xp-tube-panel/components/PoolXPTubeSubmitPanel.vue new file mode 100644 index 000000000..ab1a657bf --- /dev/null +++ b/app/frontend/javascript/pool-xp-tube-panel/components/PoolXPTubeSubmitPanel.vue @@ -0,0 +1,445 @@ + + + diff --git a/app/frontend/javascript/pool-xp-tube-panel/index.js b/app/frontend/javascript/pool-xp-tube-panel/index.js new file mode 100644 index 000000000..e464821cb --- /dev/null +++ b/app/frontend/javascript/pool-xp-tube-panel/index.js @@ -0,0 +1,50 @@ +/* eslint no-console: 0 */ + +import Vue from 'vue' +import { BootstrapVue, BootstrapVueIcons } from 'bootstrap-vue' +import 'bootstrap/dist/css/bootstrap.css' +import 'bootstrap-vue/dist/bootstrap-vue.css' +import cookieJar from '@/javascript/shared/cookieJar.js' +import PoolXPTubeSubmitPanel from './components/PoolXPTubeSubmitPanel.vue' + +Vue.use(BootstrapVue) +Vue.use(BootstrapVueIcons) +document.addEventListener('DOMContentLoaded', async () => { + const assetElem = document.getElementById('pool-xp-tube-submit-panel') + const missingUserIdError = ` + Unfortunately Limber can't find your user id, which is required to add custom metadata. + Click log out and swipe in again to resolve this. + If this problem occurs repeatedly, let us know. + ` + + if (assetElem) { + /* The labware-custom-metadata element isn't on all pages. So only initialize our + * Vue app if we actually find it */ + const userId = cookieJar(document.cookie).user_id + const sequencescapeApiUrl = assetElem.dataset.sequencescapeApi + const tractionServiceUrl = assetElem.dataset.tractionServiceUrl + const tractionUIUrl = assetElem.dataset.tractionUiUrl + // UserId is required to make custom metadata, but will not be present in + // older session cookies. To avoid errors or confusion, we render + // a very basic vue component (essentially just an error message) + // if userId is missing + + if (userId) { + new Vue({ + el: '#pool-xp-tube-submit-panel', + render(h) { + let barcode = this.$el.dataset.barcode + + return h(PoolXPTubeSubmitPanel, { + props: { barcode, userId, sequencescapeApiUrl, tractionServiceUrl, tractionUIUrl }, + }) + }, + }) + } else { + new Vue({ + el: '#pool-xp-tube-submit-panel', + render: (h) => h('div', missingUserIdError), + }) + } + } +}) diff --git a/app/models/presenters/pcr_pool_xp_presenter.rb b/app/models/presenters/pcr_pool_xp_presenter.rb new file mode 100644 index 000000000..cce6fa01c --- /dev/null +++ b/app/models/presenters/pcr_pool_xp_presenter.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Presenters + # PcrPoolXpPresenter + # This class is responsible for presenting PCR Pool XP tube data. + # It inherits from FinalTubePresenter and provides methods + # to export data to Traction. + class PcrPoolXpPresenter < FinalTubePresenter + # Enables the export of the PCR Pool XP tube to Traction if tube is in passed state. + def export_to_traction + state == 'passed' + end + end +end diff --git a/app/views/tubes/sidebars/_default.html.erb b/app/views/tubes/sidebars/_default.html.erb index de8408cff..e69e7a06e 100644 --- a/app/views/tubes/sidebars/_default.html.erb +++ b/app/views/tubes/sidebars/_default.html.erb @@ -2,6 +2,17 @@ <%= render partial: 'tube_printing' %> <%= render(partial: 'labware/qc_data_upload') %> + <% if presenter.try(:export_to_traction)%> + <%= card title: 'Export to Traction' do %> +
+
+ <%end%> +<%end%> <%= card title: 'QC Information' do %>
<%= simple_state_change_form(@presenter) %> <% end %> + <% end %> + <%= card title: 'Adding a Comment' do %>
<% end %> diff --git a/config/environments/development.rb b/config/environments/development.rb index 72c5ef6fe..afcb2b5fe 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -80,6 +80,8 @@ def rewrite_localhost(url) config.request_options = { 'read_length' => 11 } config.pmb_uri = ENV.fetch('PMB_URI', rewrite_localhost('http://localhost:3002/v1/')) config.sprint_uri = 'http://sprint.psd.sanger.ac.uk/graphql' + config.traction_ui_uri = 'http://localhost:5173/#' + config.traction_service_uri = 'http://localhost:3100/v1' # Enable 'work in progress' pipelines by default in development mode, to save having to rename files. # Configured for other environments in the deployment project. diff --git a/config/environments/test.rb b/config/environments/test.rb index ed6751e91..1eecb6eb1 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -73,4 +73,6 @@ config.disable_animations = true config.sprint_uri = 'http://example_sprint.com/graphql' + config.traction_ui_uri = 'http://localhost:5173/#' + config.traction_service_uri = 'http://localhost:3100/v1' end diff --git a/config/pipelines/bespoke_hic.wip.yml b/config/pipelines/bespoke_hic.wip.yml new file mode 100644 index 000000000..85c098668 --- /dev/null +++ b/config/pipelines/bespoke_hic.wip.yml @@ -0,0 +1,7 @@ +Bespoke PCR MX HiC: + filters: + request_type_key: limber_multiplexing_hic + relationships: + LBB Lib PCR-XP: LBB Lib PCR-XP Norm + LBB Lib PCR-XP Norm: LBB Lib Pool Stock + LBB Lib Pool Stock: LB Lib Pool Norm diff --git a/config/pipelines/rna_rnaf.yml b/config/pipelines/rna_rnaf.yml new file mode 100644 index 000000000..b49266801 --- /dev/null +++ b/config/pipelines/rna_rnaf.yml @@ -0,0 +1,15 @@ +--- +RNA FFPE: + pipeline_group: RNA + filters: + request_type_key: limber_rnaf + library_type: RNA FFPE + library_pass: LB Lib PCR-XP + relationships: + LBR Cherrypick: LRNAF Frag + LRNAF Frag: LB cDNA + LB cDNA: LB cDNA XP + LB cDNA XP: LB End Prep + LB End Prep: LB Lib PCR + LB Lib PCR: LB Lib PCR-XP +# Then feeds into the standard limber_multiplexing diff --git a/config/purposes/bespoke_hic.wip.yml b/config/purposes/bespoke_hic.wip.yml new file mode 100644 index 000000000..7732bebdd --- /dev/null +++ b/config/purposes/bespoke_hic.wip.yml @@ -0,0 +1,3 @@ +--- +LBB Lib PCR-XP Norm: + :asset_type: plate diff --git a/config/purposes/bioscan.yml b/config/purposes/bioscan.yml index 5d33464f4..1d9e3f936 100644 --- a/config/purposes/bioscan.yml +++ b/config/purposes/bioscan.yml @@ -158,7 +158,7 @@ LBSN-9216 Lib PCR Pool XP: :target: MultiplexedLibraryTube :type: IlluminaHtp::MxTubePurpose :creator_class: LabwareCreators::TubeFromTube - :presenter_class: Presenters::FinalTubePresenter + :presenter_class: Presenters::PcrPoolXpPresenter :file_links: - name: 'Download MBrave UMI file' id: 'bioscan_mbrave' diff --git a/config/purposes/rna_plates.yml b/config/purposes/rna_plates.yml index bd317cb38..8e95a73fd 100644 --- a/config/purposes/rna_plates.yml +++ b/config/purposes/rna_plates.yml @@ -30,3 +30,6 @@ LB cDNA: LB cDNA XP: :asset_type: plate :presenter_class: Presenters::PermissivePresenter +LRNAF Frag: + :asset_type: plate + :presenter_class: Presenters::PermissivePresenter diff --git a/config/robots.rb b/config/robots.rb index bd18dc2bf..7e25c2491 100644 --- a/config/robots.rb +++ b/config/robots.rb @@ -3626,16 +3626,16 @@ name: 'Hamilton LRC PBMC Pools (or Input) => LRC GEM-X 5p Chip', require_robot: true, beds: { - bed(8).barcode => { + bed(5).barcode => { purpose: ['LRC PBMC Pools', 'LRC PBMC Pools Input'], states: ['passed'], - label: 'Bed 8' + label: 'Bed 5' }, bed(15).barcode => { purpose: 'LRC GEM-X 5p Chip', states: ['pending'], label: 'Bed 15', - parent: bed(8).barcode, + parent: bed(5).barcode, target_state: 'passed' } } @@ -3655,10 +3655,10 @@ states: ['passed'], label: 'Bed 15' }, - bed(5).barcode => { + bed(4).barcode => { purpose: 'LRC GEM-X 5p GEMs', states: ['pending'], - label: 'Bed 5', + label: 'Bed 4', parent: bed(15).barcode, target_state: 'passed' } @@ -4043,4 +4043,50 @@ } } ) + + custom_robot( + 'bravo-lbr-cherrypick-to-lrnaf-frag', + name: 'Bravo LBR Cherrypick => LRNAF Frag', + require_robot: true, + beds: { + bed(8).barcode => { + purpose: 'LBR Cherrypick', + states: ['passed'], + label: 'Bed 8' + }, + car('2,3').barcode => { + purpose: 'LRNAF Frag', + states: ['pending'], + label: 'Carousel 2,3', + parent: bed(8).barcode, + target_state: 'passed' + } + } + ) + + custom_robot( + 'bravo-lrnaf-frag-to-lb-cdna', + name: 'Bravo LRNAF Frag => LB cDNA', + require_robot: true, + beds: { + bed('8').barcode => { + purpose: 'LRNAF Frag', + states: ['passed'], + label: 'Bed 8' + }, + car('3,4').barcode => { + purpose: 'LB cDNA', + states: ['pending'], + label: 'Carousel 3,4', + parent: bed(8).barcode, + target_state: 'started' + }, + car('4,3').barcode => { + purpose: 'LB cDNA XP', + states: ['pending'], + label: 'Carousel 4,3', + parent: car('3,4').barcode + } + } + ) end diff --git a/package.json b/package.json index 7b7c3a0d2..db0c80528 100644 --- a/package.json +++ b/package.json @@ -17,14 +17,14 @@ "devDependencies": { "@prettier/plugin-ruby": "^4.0.0", "@vitejs/plugin-vue2": "^2.3.1", - "@vitest/coverage-v8": "^2.0.5", + "@vitest/coverage-v8": "^2.1.4", "@vue/test-utils": "^1.3.6", "autoprefixer": "^10.4.19", "axios-mock-adapter": "^1.22.0", "eslint": "^8.0.0", "eslint-config-prettier": "^8.10.0", "eslint-plugin-vitest": "^0.5.4", - "eslint-plugin-vue": "^9.0.0", + "eslint-plugin-vue": "^9.30.0", "jsdom": "^24.1.2", "postcss": "^8.4.47", "postcss-flexbugs-fixes": "^5.0.2", @@ -34,9 +34,9 @@ "prettier": "^3.0.0", "sass": "^1.77.8", "typescript": "^4.6.3", - "vite": "^5.0.0", + "vite": "^5.4.10", "vite-plugin-ruby": "^5.1.0", - "vitest": "^2.0.5", + "vitest": "^2.1.4", "vue-template-compiler": "^2.7.0" }, "_comment": "Required to address https://github.com/npm/cli/issues/4828", diff --git a/spec/models/presenters/pcr_pool_xp_presenter_spec.rb b/spec/models/presenters/pcr_pool_xp_presenter_spec.rb new file mode 100644 index 000000000..653a7272c --- /dev/null +++ b/spec/models/presenters/pcr_pool_xp_presenter_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'presenters/tube_presenter' +require_relative 'shared_labware_presenter_examples' + +RSpec.describe Presenters::PcrPoolXpPresenter do + let(:labware) do + build :v2_tube, purpose_name: purpose_name, state: state, barcode_number: 6, created_at: '2016-10-19 12:00:00 +0100' + end + + before { create(:stock_plate_config, uuid: 'stock-plate-purpose-uuid') } + + let(:purpose_name) { 'Limber example purpose' } + let(:title) { purpose_name } + let(:state) { 'passed' } + let(:summary_tab) do + [ + ['Barcode', 'NT6T 3980000006844'], + ['Tube type', purpose_name], + ['Current tube state', state], + ['Input plate barcode', labware.stock_plate.human_barcode], + ['Created on', '2016-10-19'] + ] + end + let(:sidebar_partial) { 'default' } + + subject { Presenters::PcrPoolXpPresenter.new(labware:) } + + it_behaves_like 'a labware presenter' + + it 'has export_to_traction option' do + expect(subject.export_to_traction).to be_truthy + end + + it 'has no export_to_traction option' do + labware.state = 'pending' + expect(subject.export_to_traction).to be_falsey + end +end diff --git a/yarn.lock b/yarn.lock index a2d8a360a..ae736f012 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,36 +10,35 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" -"@babel/helper-string-parser@^7.24.8": - version "7.24.8" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz#5b3329c9a58803d5df425e5785865881a81ca48d" - integrity sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ== +"@babel/helper-string-parser@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c" + integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== -"@babel/helper-validator-identifier@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" - integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== +"@babel/helper-validator-identifier@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" + integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== "@babel/parser@^7.23.5": version "7.23.6" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.6.tgz#ba1c9e512bda72a47e285ae42aff9d2a635a9e3b" integrity sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ== -"@babel/parser@^7.24.4": - version "7.25.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.3.tgz#91fb126768d944966263f0657ab222a642b82065" - integrity sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw== +"@babel/parser@^7.25.4": + version "7.26.2" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.2.tgz#fd7b6f487cfea09889557ef5d4eeb9ff9a5abd11" + integrity sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ== dependencies: - "@babel/types" "^7.25.2" + "@babel/types" "^7.26.0" -"@babel/types@^7.24.0", "@babel/types@^7.25.2": - version "7.25.2" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.2.tgz#55fb231f7dc958cd69ea141a4c2997e819646125" - integrity sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q== +"@babel/types@^7.25.4", "@babel/types@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.0.tgz#deabd08d6b753bc8e0f198f8709fb575e31774ff" + integrity sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA== dependencies: - "@babel/helper-string-parser" "^7.24.8" - "@babel/helper-validator-identifier" "^7.24.7" - to-fast-properties "^2.0.0" + "@babel/helper-string-parser" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" "@bcoe/v8-coverage@^0.2.3": version "0.2.3" @@ -780,73 +779,81 @@ resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue2/-/plugin-vue2-2.3.1.tgz#53078d3d9d50d9863f1fbb1c1ef7791a5fcd4948" integrity sha512-/ksaaz2SRLN11JQhLdEUhDzOn909WEk99q9t9w+N12GjQCljzv7GyvAbD/p20aBUjHkvpGOoQ+FCOkG+mjDF4A== -"@vitest/coverage-v8@^2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@vitest/coverage-v8/-/coverage-v8-2.0.5.tgz#411961ce4fd1177a32b4dd74ab576ed3b859155e" - integrity sha512-qeFcySCg5FLO2bHHSa0tAZAOnAUbp4L6/A5JDuj9+bt53JREl8hpLjLHEWF0e/gWc8INVpJaqA7+Ene2rclpZg== +"@vitest/coverage-v8@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@vitest/coverage-v8/-/coverage-v8-2.1.4.tgz#c0df11cda12b3a04570e8065754917d35baa0c55" + integrity sha512-FPKQuJfR6VTfcNMcGpqInmtJuVXFSCd9HQltYncfR01AzXhLucMEtQ5SinPdZxsT5x/5BK7I5qFJ5/ApGCmyTQ== dependencies: "@ampproject/remapping" "^2.3.0" "@bcoe/v8-coverage" "^0.2.3" - debug "^4.3.5" + debug "^4.3.7" istanbul-lib-coverage "^3.2.2" istanbul-lib-report "^3.0.1" istanbul-lib-source-maps "^5.0.6" istanbul-reports "^3.1.7" - magic-string "^0.30.10" - magicast "^0.3.4" + magic-string "^0.30.12" + magicast "^0.3.5" std-env "^3.7.0" test-exclude "^7.0.1" tinyrainbow "^1.2.0" -"@vitest/expect@2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-2.0.5.tgz#f3745a6a2c18acbea4d39f5935e913f40d26fa86" - integrity sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA== +"@vitest/expect@2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-2.1.4.tgz#48f4f53a01092a3bdc118cff245f79ef388bdd8e" + integrity sha512-DOETT0Oh1avie/D/o2sgMHGrzYUFFo3zqESB2Hn70z6QB1HrS2IQ9z5DfyTqU8sg4Bpu13zZe9V4+UTNQlUeQA== dependencies: - "@vitest/spy" "2.0.5" - "@vitest/utils" "2.0.5" - chai "^5.1.1" + "@vitest/spy" "2.1.4" + "@vitest/utils" "2.1.4" + chai "^5.1.2" tinyrainbow "^1.2.0" -"@vitest/pretty-format@2.0.5", "@vitest/pretty-format@^2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-2.0.5.tgz#91d2e6d3a7235c742e1a6cc50e7786e2f2979b1e" - integrity sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ== +"@vitest/mocker@2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-2.1.4.tgz#0dc07edb9114f7f080a0181fbcdb16cd4a2d855d" + integrity sha512-Ky/O1Lc0QBbutJdW0rqLeFNbuLEyS+mIPiNdlVlp2/yhJ0SbyYqObS5IHdhferJud8MbbwMnexg4jordE5cCoQ== + dependencies: + "@vitest/spy" "2.1.4" + estree-walker "^3.0.3" + magic-string "^0.30.12" + +"@vitest/pretty-format@2.1.4", "@vitest/pretty-format@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-2.1.4.tgz#fc31993bdc1ef5a6c1a4aa6844e7ba55658a4f9f" + integrity sha512-L95zIAkEuTDbUX1IsjRl+vyBSLh3PwLLgKpghl37aCK9Jvw0iP+wKwIFhfjdUtA2myLgjrG6VU6JCFLv8q/3Ww== dependencies: tinyrainbow "^1.2.0" -"@vitest/runner@2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-2.0.5.tgz#89197e712bb93513537d6876995a4843392b2a84" - integrity sha512-TfRfZa6Bkk9ky4tW0z20WKXFEwwvWhRY+84CnSEtq4+3ZvDlJyY32oNTJtM7AW9ihW90tX/1Q78cb6FjoAs+ig== +"@vitest/runner@2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-2.1.4.tgz#f9346500bdd0be1c926daaac5d683bae87ceda2c" + integrity sha512-sKRautINI9XICAMl2bjxQM8VfCMTB0EbsBc/EDFA57V6UQevEKY/TOPOF5nzcvCALltiLfXWbq4MaAwWx/YxIA== dependencies: - "@vitest/utils" "2.0.5" + "@vitest/utils" "2.1.4" pathe "^1.1.2" -"@vitest/snapshot@2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-2.0.5.tgz#a2346bc5013b73c44670c277c430e0334690a162" - integrity sha512-SgCPUeDFLaM0mIUHfaArq8fD2WbaXG/zVXjRupthYfYGzc8ztbFbu6dUNOblBG7XLMR1kEhS/DNnfCZ2IhdDew== +"@vitest/snapshot@2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-2.1.4.tgz#ef8c3f605fbc23a32773256d37d3fdfd9b23d353" + integrity sha512-3Kab14fn/5QZRog5BPj6Rs8dc4B+mim27XaKWFWHWA87R56AKjHTGcBFKpvZKDzC4u5Wd0w/qKsUIio3KzWW4Q== dependencies: - "@vitest/pretty-format" "2.0.5" - magic-string "^0.30.10" + "@vitest/pretty-format" "2.1.4" + magic-string "^0.30.12" pathe "^1.1.2" -"@vitest/spy@2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-2.0.5.tgz#590fc07df84a78b8e9dd976ec2090920084a2b9f" - integrity sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA== +"@vitest/spy@2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-2.1.4.tgz#4e90f9783437c5841a27c80f8fd84d7289a6100a" + integrity sha512-4JOxa+UAizJgpZfaCPKK2smq9d8mmjZVPMt2kOsg/R8QkoRzydHH1qHxIYNvr1zlEaFj4SXiaaJWxq/LPLKaLg== dependencies: - tinyspy "^3.0.0" + tinyspy "^3.0.2" -"@vitest/utils@2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-2.0.5.tgz#6f8307a4b6bc6ceb9270007f73c67c915944e926" - integrity sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ== +"@vitest/utils@2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-2.1.4.tgz#6d67ac966647a21ce8bc497472ce230de3b64537" + integrity sha512-MXDnZn0Awl2S86PSNIim5PWXgIAx8CIkzu35mBdSApUip6RFOGXBCf3YFyeEu8n1IHk4bWD46DeYFu9mQlFIRg== dependencies: - "@vitest/pretty-format" "2.0.5" - estree-walker "^3.0.3" - loupe "^3.1.1" + "@vitest/pretty-format" "2.1.4" + loupe "^3.1.2" tinyrainbow "^1.2.0" "@vue/compiler-sfc@2.7.16": @@ -1079,10 +1086,10 @@ caniuse-lite@^1.0.30001640: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001641.tgz#3572862cd18befae3f637f2a1101cc033c6782ac" integrity sha512-Phv5thgl67bHYo1TtMY/MurjkHhV4EDaCosezRXgZ8jzA/Ub+wjxAvbGvjoFENStinwi5kCyOYV3mi5tOGykwA== -chai@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/chai/-/chai-5.1.1.tgz#f035d9792a22b481ead1c65908d14bb62ec1c82c" - integrity sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA== +chai@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/chai/-/chai-5.1.2.tgz#3afbc340b994ae3610ca519a6c70ace77ad4378d" + integrity sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw== dependencies: assertion-error "^2.0.1" check-error "^2.1.1" @@ -1169,7 +1176,7 @@ consola@^2.15.0: resolved "https://registry.yarnpkg.com/consola/-/consola-2.15.3.tgz#2e11f98d6a4be71ff72e0bdf07bd23e12cb61550" integrity sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw== -cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: +cross-spawn@^7.0.0, cross-spawn@^7.0.2: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -1253,7 +1260,7 @@ de-indent@^1.0.2: resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d" integrity sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg== -debug@4, debug@^4.1.1, debug@^4.3.5: +debug@4, debug@^4.1.1: version "4.3.6" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b" integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg== @@ -1274,6 +1281,13 @@ debug@^4.3.2: dependencies: ms "2.1.2" +debug@^4.3.7: + version "4.3.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== + dependencies: + ms "^2.1.3" + decimal.js@^10.4.3: version "10.4.3" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" @@ -1430,17 +1444,18 @@ eslint-plugin-vitest@^0.5.4: dependencies: "@typescript-eslint/utils" "^7.7.1" -eslint-plugin-vue@^9.0.0: - version "9.20.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.20.1.tgz#7ed78846898574b2cd26939f28b0b87798a7b528" - integrity sha512-GyCs8K3lkEvoyC1VV97GJhP1SvqsKCiWGHnbn0gVUYiUhaH2+nB+Dv1uekv1THFMPbBfYxukrzQdltw950k+LQ== +eslint-plugin-vue@^9.30.0: + version "9.30.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.30.0.tgz#801a7d2d7d3bb878fcf71932a1e11fd07529e2c4" + integrity sha512-CyqlRgShvljFkOeYK8wN5frh/OGTvkj1S7wlr2Q2pUvwq+X5VYiLd6ZjujpgSgLnys2W8qrBLkXQ41SUYaoPIQ== dependencies: "@eslint-community/eslint-utils" "^4.4.0" + globals "^13.24.0" natural-compare "^1.4.0" nth-check "^2.1.1" - postcss-selector-parser "^6.0.13" - semver "^7.5.4" - vue-eslint-parser "^9.4.0" + postcss-selector-parser "^6.0.15" + semver "^7.6.3" + vue-eslint-parser "^9.4.3" xml-name-validator "^4.0.0" eslint-scope@^7.1.1, eslint-scope@^7.2.2: @@ -1547,20 +1562,10 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -execa@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c" - integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^8.0.1" - human-signals "^5.0.0" - is-stream "^3.0.0" - merge-stream "^2.0.0" - npm-run-path "^5.1.0" - onetime "^6.0.0" - signal-exit "^4.1.0" - strip-final-newline "^3.0.0" +expect-type@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.1.0.tgz#a146e414250d13dfc49eafcfd1344a4060fa4c75" + integrity sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA== extend-shallow@^2.0.1: version "2.0.1" @@ -1700,11 +1705,6 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@ has-symbols "^1.0.3" hasown "^2.0.0" -get-stream@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2" - integrity sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA== - glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -1754,7 +1754,7 @@ glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -globals@^13.19.0: +globals@^13.19.0, globals@^13.24.0: version "13.24.0" resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== @@ -1847,11 +1847,6 @@ https-proxy-agent@^7.0.5: agent-base "^7.0.2" debug "4" -human-signals@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28" - integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== - iconv-lite@0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" @@ -1961,11 +1956,6 @@ is-potential-custom-element-name@^1.0.1: resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== -is-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" - integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== - is-whitespace@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/is-whitespace/-/is-whitespace-0.3.0.tgz#1639ecb1be036aec69a54cbb401cfbed7114ab7f" @@ -2128,13 +2118,18 @@ lodash@^4.17.15, lodash@^4.17.21: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -loupe@^3.1.0, loupe@^3.1.1: +loupe@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.1.1.tgz#71d038d59007d890e3247c5db97c1ec5a92edc54" integrity sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw== dependencies: get-func-name "^2.0.1" +loupe@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.1.2.tgz#c86e0696804a02218f2206124c45d8b15291a240" + integrity sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg== + lru-cache@^10.2.0: version "10.4.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" @@ -2152,20 +2147,20 @@ lru-cache@^6.0.0: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.1.0.tgz#2098d41c2dc56500e6c88584aa656c84de7d0484" integrity sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag== -magic-string@^0.30.10: - version "0.30.11" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.11.tgz#301a6f93b3e8c2cb13ac1a7a673492c0dfd12954" - integrity sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A== +magic-string@^0.30.12: + version "0.30.12" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.12.tgz#9eb11c9d072b9bcb4940a5b2c2e1a217e4ee1a60" + integrity sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw== dependencies: "@jridgewell/sourcemap-codec" "^1.5.0" -magicast@^0.3.4: - version "0.3.4" - resolved "https://registry.yarnpkg.com/magicast/-/magicast-0.3.4.tgz#bbda1791d03190a24b00ff3dd18151e7fd381d19" - integrity sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q== +magicast@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/magicast/-/magicast-0.3.5.tgz#8301c3c7d66704a0771eb1bad74274f0ec036739" + integrity sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ== dependencies: - "@babel/parser" "^7.24.4" - "@babel/types" "^7.24.0" + "@babel/parser" "^7.25.4" + "@babel/types" "^7.25.4" source-map-js "^1.2.0" make-dir@^4.0.0: @@ -2175,11 +2170,6 @@ make-dir@^4.0.0: dependencies: semver "^7.5.3" -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" @@ -2210,11 +2200,6 @@ mime-types@^2.1.12: dependencies: mime-db "1.52.0" -mimic-fn@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" - integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== - minilog@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/minilog/-/minilog-3.1.0.tgz#d2d0f1887ca363d1acf0ea86d5c4df293b3fb675" @@ -2265,6 +2250,11 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + nanoid@^3.3.7: version "3.3.7" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" @@ -2304,13 +2294,6 @@ normalize-range@^0.1.2: resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== -npm-run-path@^5.1.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.3.0.tgz#e23353d0ebb9317f174e93417e4a4d82d0249e9f" - integrity sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ== - dependencies: - path-key "^4.0.0" - nth-check@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" @@ -2335,13 +2318,6 @@ once@^1.3.0: dependencies: wrappy "1" -onetime@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4" - integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== - dependencies: - mimic-fn "^4.0.0" - optionator@^0.9.3: version "0.9.4" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" @@ -2402,11 +2378,6 @@ path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-key@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" - integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== - path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" @@ -2782,6 +2753,14 @@ postcss-selector-parser@^6.0.13: cssesc "^3.0.0" util-deprecate "^1.0.2" +postcss-selector-parser@^6.0.15: + version "6.1.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz#27ecb41fb0e3b6ba7a1ec84fff347f734c7929de" + integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" @@ -2974,14 +2953,14 @@ saxes@^6.0.0: dependencies: xmlchars "^2.2.0" -semver@^7.3.6, semver@^7.5.3, semver@^7.5.4: +semver@^7.3.6, semver@^7.5.3: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== dependencies: lru-cache "^6.0.0" -semver@^7.6.0: +semver@^7.6.0, semver@^7.6.3: version "7.6.3" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== @@ -3023,7 +3002,7 @@ siginfo@^2.0.0: resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== -signal-exit@^4.0.1, signal-exit@^4.1.0: +signal-exit@^4.0.1: version "4.1.0" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== @@ -3097,11 +3076,6 @@ strip-ansi@^7.0.1: dependencies: ansi-regex "^6.0.1" -strip-final-newline@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" - integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== - strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" @@ -3138,30 +3112,30 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== -tinybench@^2.8.0: +tinybench@^2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b" integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg== -tinypool@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.0.0.tgz#a68965218e04f4ad9de037d2a1cd63cda9afb238" - integrity sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ== +tinyexec@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.1.tgz#0ab0daf93b43e2c211212396bdb836b468c97c98" + integrity sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ== + +tinypool@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.0.1.tgz#c64233c4fac4304e109a64340178760116dbe1fe" + integrity sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA== tinyrainbow@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-1.2.0.tgz#5c57d2fc0fb3d1afd78465c33ca885d04f02abb5" integrity sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ== -tinyspy@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-3.0.0.tgz#cb61644f2713cd84dee184863f4642e06ddf0585" - integrity sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA== - -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== +tinyspy@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-3.0.2.tgz#86dd3cf3d737b15adcf17d7887c84a75201df20a" + integrity sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q== to-regex-range@^5.0.1: version "5.0.1" @@ -3254,15 +3228,14 @@ util-deprecate@^1.0.2: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -vite-node@2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-2.0.5.tgz#36d909188fc6e3aba3da5fc095b3637d0d18e27b" - integrity sha512-LdsW4pxj0Ot69FAoXZ1yTnA9bjGohr2yNBU7QKRxpz8ITSkhuDl6h3zS/tvgz4qrNjeRnvrWeXQ8ZF7Um4W00Q== +vite-node@2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-2.1.4.tgz#97ffb6de913fd8d42253afe441f9512e9dbdfd5c" + integrity sha512-kqa9v+oi4HwkG6g8ufRnb5AeplcRw8jUF6/7/Qz1qRQOXHImG8YnLbB+LLszENwFnoBl9xIf9nVdCFzNd7GQEg== dependencies: cac "^6.7.14" - debug "^4.3.5" + debug "^4.3.7" pathe "^1.1.2" - tinyrainbow "^1.2.0" vite "^5.0.0" vite-plugin-ruby@^5.1.0: @@ -3284,35 +3257,47 @@ vite@^5.0.0: optionalDependencies: fsevents "~2.3.3" -vitest@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/vitest/-/vitest-2.0.5.tgz#2f15a532704a7181528e399cc5b754c7f335fd62" - integrity sha512-8GUxONfauuIdeSl5f9GTgVEpg5BTOlplET4WEDaeY2QBiN8wSm68vxN/tb5z405OwppfoCavnwXafiaYBC/xOA== +vite@^5.4.10: + version "5.4.10" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.10.tgz#d358a7bd8beda6cf0f3b7a450a8c7693a4f80c18" + integrity sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ== dependencies: - "@ampproject/remapping" "^2.3.0" - "@vitest/expect" "2.0.5" - "@vitest/pretty-format" "^2.0.5" - "@vitest/runner" "2.0.5" - "@vitest/snapshot" "2.0.5" - "@vitest/spy" "2.0.5" - "@vitest/utils" "2.0.5" - chai "^5.1.1" - debug "^4.3.5" - execa "^8.0.1" - magic-string "^0.30.10" + esbuild "^0.21.3" + postcss "^8.4.43" + rollup "^4.20.0" + optionalDependencies: + fsevents "~2.3.3" + +vitest@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-2.1.4.tgz#ba8f4589fb639cf5a9e6af54781667312b3e8230" + integrity sha512-eDjxbVAJw1UJJCHr5xr/xM86Zx+YxIEXGAR+bmnEID7z9qWfoxpHw0zdobz+TQAFOLT+nEXz3+gx6nUJ7RgmlQ== + dependencies: + "@vitest/expect" "2.1.4" + "@vitest/mocker" "2.1.4" + "@vitest/pretty-format" "^2.1.4" + "@vitest/runner" "2.1.4" + "@vitest/snapshot" "2.1.4" + "@vitest/spy" "2.1.4" + "@vitest/utils" "2.1.4" + chai "^5.1.2" + debug "^4.3.7" + expect-type "^1.1.0" + magic-string "^0.30.12" pathe "^1.1.2" std-env "^3.7.0" - tinybench "^2.8.0" - tinypool "^1.0.0" + tinybench "^2.9.0" + tinyexec "^0.3.1" + tinypool "^1.0.1" tinyrainbow "^1.2.0" vite "^5.0.0" - vite-node "2.0.5" + vite-node "2.1.4" why-is-node-running "^2.3.0" -vue-eslint-parser@^9.4.0: - version "9.4.0" - resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-9.4.0.tgz#dfd22302e2992fe45748a76553cef7afa5bdde27" - integrity sha512-7KsNBb6gHFA75BtneJsoK/dbZ281whUIwFYdQxA68QrCrGMXYzUMbPDHGcOQ0OocIVKrWSKWXZ4mL7tonCXoUw== +vue-eslint-parser@^9.4.3: + version "9.4.3" + resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz#9b04b22c71401f1e8bca9be7c3e3416a4bde76a8" + integrity sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg== dependencies: debug "^4.3.4" eslint-scope "^7.1.1"