mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2026-02-06 09:55:50 +00:00
fix(webapp): allow running frontend tests locally (#9125)
This commit is contained in:
parent
ba481547f1
commit
af497deb77
@ -46,6 +46,7 @@ ONBUILD RUN cp -r ./constants /build
|
||||
ONBUILD RUN cp -r ./static /build
|
||||
ONBUILD RUN cp -r ./locales /build
|
||||
ONBUILD RUN cp -r ./package.json ./yarn.lock /build
|
||||
ONBUILD RUN cp -r ./scripts /build
|
||||
ONBUILD RUN cd /build && yarn install --production=true --frozen-lockfile --non-interactive
|
||||
|
||||
FROM base AS test_build
|
||||
@ -62,6 +63,7 @@ RUN cp -r ./constants /build
|
||||
RUN cp -r ./static /build
|
||||
RUN cp -r ./locales /build
|
||||
RUN cp -r ./package.json ./yarn.lock /build
|
||||
RUN cp -r ./scripts /build
|
||||
RUN cd /build && yarn install --frozen-lockfile --non-interactive
|
||||
|
||||
FROM test_build AS test
|
||||
|
||||
@ -62,6 +62,9 @@ describe('CommentCard.vue', () => {
|
||||
Wrapper = () => {
|
||||
const store = new Vuex.Store({
|
||||
getters,
|
||||
actions: {
|
||||
'pinnedPosts/fetch': jest.fn(),
|
||||
},
|
||||
})
|
||||
return mount(CommentCard, {
|
||||
store,
|
||||
|
||||
@ -50,9 +50,12 @@ describe('CommentList.vue', () => {
|
||||
return { id: 'some-user' }
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
'pinnedPosts/fetch': jest.fn(),
|
||||
},
|
||||
})
|
||||
mocks = {
|
||||
$t: jest.fn(),
|
||||
$t: (key) => key,
|
||||
$filters: {
|
||||
truncate: (a) => a,
|
||||
removeHtml: (a) => a,
|
||||
|
||||
@ -4,6 +4,10 @@ import Component from './CtaUnblockAuthor.vue'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const stubs = {
|
||||
'nuxt-link': true,
|
||||
}
|
||||
|
||||
describe('CtaUnblockAuthor.vue', () => {
|
||||
let propsData, wrapper, mocks
|
||||
|
||||
@ -21,7 +25,7 @@ describe('CtaUnblockAuthor.vue', () => {
|
||||
})
|
||||
|
||||
const Wrapper = () => {
|
||||
return shallowMount(Component, { propsData, localVue, mocks })
|
||||
return shallowMount(Component, { propsData, localVue, mocks, stubs })
|
||||
}
|
||||
|
||||
describe('shallowMount', () => {
|
||||
|
||||
@ -9,10 +9,10 @@ exports[`CtaUnblockAuthor.vue shallowMount renders 1`] = `
|
||||
<ds-text-stub tag="p">
|
||||
contribution.comment.commenting-disabled.blocked-author.call-to-action
|
||||
</ds-text-stub>
|
||||
<nuxt-link to="[object Object]">
|
||||
<nuxt-link-stub to="[object Object]">
|
||||
<base-button-stub filled="true" icon="arrow-right" size="regular" type="button">
|
||||
contribution.comment.commenting-disabled.blocked-author.button-label
|
||||
</base-button-stub>
|
||||
</nuxt-link>
|
||||
</nuxt-link-stub>
|
||||
</ds-space-stub>
|
||||
`;
|
||||
|
||||
@ -4,7 +4,7 @@ import LocationInfo from './LocationInfo.vue'
|
||||
const localVue = global.localVue
|
||||
|
||||
describe('LocationInfo', () => {
|
||||
const Wrapper = ({ withDistance }) => {
|
||||
const Wrapper = ({ withDistance, size = 'base', isOwner = false }) => {
|
||||
return render(LocationInfo, {
|
||||
localVue,
|
||||
propsData: {
|
||||
@ -12,6 +12,8 @@ describe('LocationInfo', () => {
|
||||
name: 'Paris',
|
||||
distanceToMe: withDistance ? 100 : null,
|
||||
},
|
||||
size,
|
||||
isOwner,
|
||||
},
|
||||
mocks: {
|
||||
$t: jest.fn((t) => t),
|
||||
@ -33,12 +35,12 @@ describe('LocationInfo', () => {
|
||||
|
||||
describe('size', () => {
|
||||
it('renders in base size', () => {
|
||||
const wrapper = Wrapper({ size: 'base' })
|
||||
const wrapper = Wrapper({ withDistance: false, size: 'base' })
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('renders in small size', () => {
|
||||
const wrapper = Wrapper({ size: 'small' })
|
||||
const wrapper = Wrapper({ withDistance: false, size: 'small' })
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
@ -78,7 +78,7 @@ exports[`LocationInfo size renders in base size 1`] = `
|
||||
exports[`LocationInfo size renders in small size 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="location-info size-base"
|
||||
class="location-info size-small"
|
||||
>
|
||||
<div
|
||||
class="location"
|
||||
|
||||
@ -15,10 +15,11 @@ const stubs = {
|
||||
}
|
||||
|
||||
describe('SearchResults', () => {
|
||||
let mocks, getters, propsData, wrapper
|
||||
let mocks, getters, actions, propsData, wrapper
|
||||
const Wrapper = () => {
|
||||
const store = new Vuex.Store({
|
||||
getters,
|
||||
actions,
|
||||
})
|
||||
return mount(SearchResults, { mocks, localVue, propsData, store, stubs })
|
||||
}
|
||||
@ -34,6 +35,9 @@ describe('SearchResults', () => {
|
||||
'auth/isModerator': () => false,
|
||||
'categories/categoriesActive': () => false,
|
||||
}
|
||||
actions = {
|
||||
'categories/init': jest.fn(),
|
||||
}
|
||||
propsData = {
|
||||
pageSize: 12,
|
||||
search: '',
|
||||
|
||||
@ -266,9 +266,6 @@ export default {
|
||||
** You can extend webpack config here
|
||||
*/
|
||||
extend(config, ctx) {
|
||||
// Fix composition api reference for v-mapbox
|
||||
config.resolve.alias['@vue/composition-api'] = '@nuxtjs/composition-api'
|
||||
|
||||
// Add the compilerOptions
|
||||
ctx.loaders.vue.compilerOptions = {
|
||||
// Add your compilerOptions here
|
||||
|
||||
@ -18,7 +18,8 @@
|
||||
"precommit": "yarn lint",
|
||||
"test": "cross-env NODE_ENV=test jest --coverage --forceExit --detectOpenHandles",
|
||||
"test:unit:update": "yarn test -- --updateSnapshot",
|
||||
"test:unit:debug": "node --inspect-brk ./node_modules/jest/bin/jest.js --no-cache --runInBand"
|
||||
"test:unit:debug": "node --inspect-brk ./node_modules/jest/bin/jest.js --no-cache --runInBand",
|
||||
"postinstall": "node scripts/fix-vue2-jest.js && node scripts/fix-v-mapbox.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mapbox/mapbox-gl-geocoder": "^5.0.2",
|
||||
@ -29,6 +30,7 @@
|
||||
"@nuxtjs/pwa": "^3.0.0-beta.20",
|
||||
"@nuxtjs/sentry": "^4.0.0",
|
||||
"@nuxtjs/style-resources": "~1.1.0",
|
||||
"@vue/composition-api": "^1.7.2",
|
||||
"accounting": "~0.4.1",
|
||||
"apollo-cache-inmemory": "~1.6.6",
|
||||
"apollo-client": "~2.6.10",
|
||||
|
||||
@ -7,6 +7,7 @@ const localVue = global.localVue
|
||||
const stubs = {
|
||||
'client-only': true,
|
||||
'nuxt-child': true,
|
||||
'nuxt-link': true,
|
||||
}
|
||||
|
||||
describe('password-reset.vue', () => {
|
||||
|
||||
@ -46,6 +46,7 @@ describe('PostSlug', () => {
|
||||
},
|
||||
actions: {
|
||||
'categories/init': jest.fn(),
|
||||
'pinnedPosts/fetch': jest.fn(),
|
||||
},
|
||||
})
|
||||
const propsData = {}
|
||||
|
||||
63
webapp/scripts/fix-v-mapbox.js
Normal file
63
webapp/scripts/fix-v-mapbox.js
Normal file
@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* This script patches v-mapbox to fix the templateRefs issue with Vue 2.6 + @vue/composition-api.
|
||||
*
|
||||
* The problem: v-mapbox uses `ref(context.refs)` in setup(), but context.refs is empty
|
||||
* until the component is mounted in Vue 2.6 with @vue/composition-api.
|
||||
*
|
||||
* The fix: Replace the setup function to use a reactive getter that accesses $refs at runtime.
|
||||
*/
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
const vmapboxFile = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'node_modules',
|
||||
'v-mapbox',
|
||||
'dist',
|
||||
'v-mapbox.esm.js',
|
||||
)
|
||||
|
||||
if (fs.existsSync(vmapboxFile)) {
|
||||
let content = fs.readFileSync(vmapboxFile, 'utf8')
|
||||
|
||||
// Check if already patched
|
||||
if (content.includes('// PATCHED for Vue 2.6')) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('v-mapbox already patched')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
// Find and replace the problematic setup function
|
||||
// Original: setup(_, context) { const templateRefs = ref(context.refs); return { templateRefs }; }
|
||||
const originalSetup =
|
||||
/setup\(_, context\)\s*\{\s*const templateRefs\s*=\s*ref\(context\.refs\);\s*return\s*\{\s*templateRefs\s*\};\s*\}/
|
||||
|
||||
const patchedSetup = `setup(_, context) {
|
||||
// PATCHED for Vue 2.6 + @vue/composition-api compatibility
|
||||
// Use a computed-like approach that accesses $refs at runtime
|
||||
const templateRefs = ref({});
|
||||
return { templateRefs };
|
||||
}`
|
||||
|
||||
if (originalSetup.test(content)) {
|
||||
content = content.replace(originalSetup, patchedSetup)
|
||||
|
||||
// Also patch the $_loadMap method to use this.$refs directly
|
||||
content = content.replace(
|
||||
/container:\s*this\.templateRefs\.container/g,
|
||||
'container: this.$refs.container',
|
||||
)
|
||||
|
||||
fs.writeFileSync(vmapboxFile, content)
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Patched v-mapbox for Vue 2.6 compatibility')
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('v-mapbox setup pattern not found - may already be compatible or structure changed')
|
||||
}
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('v-mapbox not installed, skipping patch')
|
||||
}
|
||||
30
webapp/scripts/fix-vue2-jest.js
Normal file
30
webapp/scripts/fix-vue2-jest.js
Normal file
@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* This script prevents vue2-jest from using Vue 3's compiler-sfc
|
||||
* when Vue 3 is installed in parent node_modules (e.g., from vuepress).
|
||||
*
|
||||
* It creates a mock module at node_modules/vue/compiler-sfc that throws
|
||||
* an error, forcing vue2-jest to fall back to @vue/component-compiler-utils.
|
||||
*/
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
const vueDir = path.join(__dirname, '..', 'node_modules', 'vue')
|
||||
const compilerSfcDir = path.join(vueDir, 'compiler-sfc')
|
||||
const indexFile = path.join(compilerSfcDir, 'index.js')
|
||||
|
||||
// Only create if vue exists but compiler-sfc doesn't
|
||||
if (fs.existsSync(vueDir) && !fs.existsSync(path.join(vueDir, 'compiler-sfc.js'))) {
|
||||
if (!fs.existsSync(compilerSfcDir)) {
|
||||
fs.mkdirSync(compilerSfcDir, { recursive: true })
|
||||
}
|
||||
|
||||
const content = `// Auto-generated by scripts/fix-vue2-jest.js
|
||||
// Prevents vue2-jest from using Vue 3's compiler-sfc from parent node_modules
|
||||
throw new Error('vue/compiler-sfc is not available in Vue 2.6')
|
||||
`
|
||||
|
||||
fs.writeFileSync(indexFile, content)
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Created vue/compiler-sfc mock for vue2-jest compatibility')
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
import Vue from 'vue'
|
||||
import { createLocalVue } from '@vue/test-utils'
|
||||
import Vuex from 'vuex'
|
||||
import VTooltip from 'v-tooltip'
|
||||
@ -10,6 +11,20 @@ import VueObserveVisibility from '~/plugins/vue-observe-visibility'
|
||||
|
||||
require('intersection-observer')
|
||||
|
||||
// Fail tests on Vue warnings
|
||||
Vue.config.warnHandler = (msg, vm, trace) => {
|
||||
throw new Error(`[Vue warn]: ${msg}${trace}`)
|
||||
}
|
||||
|
||||
// Fail tests on console.error (catches Vuex errors like "unknown action type")
|
||||
// eslint-disable-next-line no-console
|
||||
const originalConsoleError = console.error
|
||||
// eslint-disable-next-line no-console
|
||||
console.error = (...args) => {
|
||||
originalConsoleError.apply(console, args)
|
||||
throw new Error(`console.error was called: ${args.join(' ')}`)
|
||||
}
|
||||
|
||||
global.localVue = createLocalVue()
|
||||
|
||||
global.localVue.use(Vuex)
|
||||
|
||||
@ -5571,6 +5571,11 @@
|
||||
source-map "~0.6.1"
|
||||
vue-template-es2015-compiler "^1.9.0"
|
||||
|
||||
"@vue/composition-api@^1.7.2":
|
||||
version "1.7.2"
|
||||
resolved "https://registry.yarnpkg.com/@vue/composition-api/-/composition-api-1.7.2.tgz#0b656f3ec39fefc2cf40aaa8c12426bcfeae1b44"
|
||||
integrity sha512-M8jm9J/laYrYT02665HkZ5l2fWTK4dcVg3BsDHm/pfz+MjDYwX+9FUaZyGwEyXEDonQYRCo0H7aLgdklcIELjw==
|
||||
|
||||
"@vue/test-utils@1.3.4":
|
||||
version "1.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-1.3.4.tgz#83a68179178cb3da4b2b7c5c59ac660dbdff8ef5"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user