test: move mockWarn into setup files
This commit is contained in:
parent
d4527230e4
commit
5c74243211
@ -1,5 +1,6 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
setupFilesAfterEnv: ['./scripts/setupJestEnv.ts'],
|
||||
globals: {
|
||||
__DEV__: true,
|
||||
__TEST__: true,
|
||||
|
@ -7,12 +7,9 @@ import {
|
||||
compileStyleAsync,
|
||||
SFCStyleCompileOptions
|
||||
} from '../src/compileStyle'
|
||||
import { mockWarn } from '@vue/shared'
|
||||
import path from 'path'
|
||||
|
||||
describe('SFC scoped CSS', () => {
|
||||
mockWarn()
|
||||
|
||||
function compileScoped(
|
||||
source: string,
|
||||
options?: Partial<SFCStyleCompileOptions>
|
||||
|
9
packages/global.d.ts
vendored
9
packages/global.d.ts
vendored
@ -13,3 +13,12 @@ declare var __VERSION__: string
|
||||
declare var __FEATURE_OPTIONS_API__: boolean
|
||||
declare var __FEATURE_PROD_DEVTOOLS__: boolean
|
||||
declare var __FEATURE_SUSPENSE__: boolean
|
||||
|
||||
// for tests
|
||||
declare namespace jest {
|
||||
interface Matchers<R, T> {
|
||||
toHaveBeenWarned(): R
|
||||
toHaveBeenWarnedLast(): R
|
||||
toHaveBeenWarnedTimes(n: number): R
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,7 @@
|
||||
import { reactive, effect, toRaw, isReactive } from '../../src'
|
||||
import { mockWarn } from '@vue/shared'
|
||||
|
||||
describe('reactivity/collections', () => {
|
||||
describe('Map', () => {
|
||||
mockWarn()
|
||||
|
||||
test('instanceof', () => {
|
||||
const original = new Map()
|
||||
const observed = reactive(original)
|
||||
|
@ -1,10 +1,7 @@
|
||||
import { reactive, effect, isReactive, toRaw } from '../../src'
|
||||
import { mockWarn } from '@vue/shared'
|
||||
|
||||
describe('reactivity/collections', () => {
|
||||
describe('Set', () => {
|
||||
mockWarn()
|
||||
|
||||
it('instanceof', () => {
|
||||
const original = new Set()
|
||||
const observed = reactive(original)
|
||||
|
@ -7,11 +7,8 @@ import {
|
||||
WritableComputedRef,
|
||||
isReadonly
|
||||
} from '../src'
|
||||
import { mockWarn } from '@vue/shared'
|
||||
|
||||
describe('reactivity/computed', () => {
|
||||
mockWarn()
|
||||
|
||||
it('should return updated value', () => {
|
||||
const value = reactive<{ foo?: number }>({})
|
||||
const cValue = computed(() => value.foo)
|
||||
|
@ -1,11 +1,8 @@
|
||||
import { ref, isRef } from '../src/ref'
|
||||
import { reactive, isReactive, toRaw, markRaw } from '../src/reactive'
|
||||
import { mockWarn } from '@vue/shared'
|
||||
import { computed } from '../src/computed'
|
||||
|
||||
describe('reactivity/reactive', () => {
|
||||
mockWarn()
|
||||
|
||||
test('Object', () => {
|
||||
const original = { foo: 1 }
|
||||
const observed = reactive(original)
|
||||
|
@ -10,7 +10,6 @@ import {
|
||||
shallowReadonly,
|
||||
isProxy
|
||||
} from '../src'
|
||||
import { mockWarn } from '@vue/shared'
|
||||
|
||||
/**
|
||||
* @see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html
|
||||
@ -18,8 +17,6 @@ import { mockWarn } from '@vue/shared'
|
||||
type Writable<T> = { -readonly [P in keyof T]: T[P] }
|
||||
|
||||
describe('reactivity/readonly', () => {
|
||||
mockWarn()
|
||||
|
||||
describe('Object', () => {
|
||||
it('should make nested values readonly', () => {
|
||||
const original = { foo: 1, bar: { baz: 2 } }
|
||||
|
@ -13,11 +13,8 @@ import {
|
||||
getCurrentInstance,
|
||||
defineComponent
|
||||
} from '@vue/runtime-test'
|
||||
import { mockWarn } from '@vue/shared'
|
||||
|
||||
describe('api: createApp', () => {
|
||||
mockWarn()
|
||||
|
||||
test('mount', () => {
|
||||
const Comp = defineComponent({
|
||||
props: {
|
||||
|
@ -10,13 +10,9 @@ import {
|
||||
reactive
|
||||
} from '../src/index'
|
||||
import { render, nodeOps, serialize } from '@vue/runtime-test'
|
||||
import { mockWarn } from '@vue/shared'
|
||||
|
||||
// reference: https://vue-composition-api-rfc.netlify.com/api.html#provide-inject
|
||||
|
||||
describe('api: provide/inject', () => {
|
||||
mockWarn()
|
||||
|
||||
it('string keys', () => {
|
||||
const Provider = {
|
||||
setup() {
|
||||
|
@ -10,7 +10,6 @@ import {
|
||||
ref,
|
||||
defineComponent
|
||||
} from '@vue/runtime-test'
|
||||
import { mockWarn } from '@vue/shared'
|
||||
|
||||
describe('api: options', () => {
|
||||
test('data', async () => {
|
||||
@ -705,8 +704,6 @@ describe('api: options', () => {
|
||||
})
|
||||
|
||||
describe('warnings', () => {
|
||||
mockWarn()
|
||||
|
||||
test('Expected a function as watch handler', () => {
|
||||
const Comp = {
|
||||
watch: {
|
||||
|
@ -14,13 +14,10 @@ import {
|
||||
TrackOpTypes,
|
||||
TriggerOpTypes
|
||||
} from '@vue/reactivity'
|
||||
import { mockWarn } from '@vue/shared'
|
||||
|
||||
// reference: https://vue-composition-api-rfc.netlify.com/api.html#watch
|
||||
|
||||
describe('api: watch', () => {
|
||||
mockWarn()
|
||||
|
||||
it('effect', async () => {
|
||||
const state = reactive({ count: 0 })
|
||||
let dummy
|
||||
|
@ -1,13 +1,10 @@
|
||||
// Note: emits and listener fallthrough is tested in
|
||||
// ./rendererAttrsFallthrough.spec.ts.
|
||||
|
||||
import { mockWarn } from '@vue/shared'
|
||||
import { render, defineComponent, h, nodeOps } from '@vue/runtime-test'
|
||||
import { isEmitListener } from '../src/componentEmits'
|
||||
|
||||
describe('component: emit', () => {
|
||||
mockWarn()
|
||||
|
||||
test('trigger handlers', () => {
|
||||
const Foo = defineComponent({
|
||||
render() {},
|
||||
|
@ -10,11 +10,8 @@ import {
|
||||
serializeInner
|
||||
} from '@vue/runtime-test'
|
||||
import { render as domRender, nextTick } from 'vue'
|
||||
import { mockWarn } from '@vue/shared'
|
||||
|
||||
describe('component props', () => {
|
||||
mockWarn()
|
||||
|
||||
test('stateful', () => {
|
||||
let props: any
|
||||
let attrs: any
|
||||
|
@ -6,12 +6,9 @@ import {
|
||||
createApp,
|
||||
shallowReadonly
|
||||
} from '@vue/runtime-test'
|
||||
import { mockWarn } from '@vue/shared'
|
||||
import { ComponentInternalInstance } from '../src/component'
|
||||
|
||||
describe('component: proxy', () => {
|
||||
mockWarn()
|
||||
|
||||
test('data', () => {
|
||||
let instance: ComponentInternalInstance
|
||||
let instanceProxy: any
|
||||
|
@ -11,11 +11,8 @@ import {
|
||||
watchEffect
|
||||
} from '@vue/runtime-test'
|
||||
import { setErrorRecovery } from '../src/errorHandling'
|
||||
import { mockWarn } from '@vue/shared'
|
||||
|
||||
describe('error handling', () => {
|
||||
mockWarn()
|
||||
|
||||
beforeEach(() => {
|
||||
setErrorRecovery(true)
|
||||
})
|
||||
|
@ -1,9 +1,7 @@
|
||||
import { renderSlot } from '../../src/helpers/renderSlot'
|
||||
import { h } from '../../src/h'
|
||||
import { mockWarn } from '@vue/shared'
|
||||
|
||||
describe('renderSlot', () => {
|
||||
mockWarn()
|
||||
it('should render slot', () => {
|
||||
let child
|
||||
const vnode = renderSlot(
|
||||
|
@ -12,11 +12,8 @@ import {
|
||||
Comment,
|
||||
VNode
|
||||
} from '@vue/runtime-test'
|
||||
import { mockWarn } from '@vue/shared'
|
||||
|
||||
describe('resolveAssets', () => {
|
||||
mockWarn()
|
||||
|
||||
test('should work', () => {
|
||||
const FooBar = () => null
|
||||
const BarBaz = { mounted: () => null }
|
||||
|
@ -1,9 +1,6 @@
|
||||
import { toHandlers } from '../../src/helpers/toHandlers'
|
||||
import { mockWarn } from '@vue/shared'
|
||||
|
||||
describe('toHandlers', () => {
|
||||
mockWarn()
|
||||
|
||||
it('should not accept non-objects', () => {
|
||||
toHandlers(null as any)
|
||||
toHandlers(undefined as any)
|
||||
|
@ -12,7 +12,6 @@ import {
|
||||
defineComponent
|
||||
} from '@vue/runtime-dom'
|
||||
import { renderToString, SSRContext } from '@vue/server-renderer'
|
||||
import { mockWarn } from '@vue/shared'
|
||||
|
||||
function mountWithHydration(html: string, render: () => any) {
|
||||
const container = document.createElement('div')
|
||||
@ -34,8 +33,6 @@ const triggerEvent = (type: string, el: Element) => {
|
||||
}
|
||||
|
||||
describe('SSR hydration', () => {
|
||||
mockWarn()
|
||||
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = ''
|
||||
})
|
||||
|
@ -13,11 +13,8 @@ import {
|
||||
createCommentVNode,
|
||||
Fragment
|
||||
} from '@vue/runtime-dom'
|
||||
import { mockWarn } from '@vue/shared'
|
||||
|
||||
describe('attribute fallthrough', () => {
|
||||
mockWarn()
|
||||
|
||||
it('should allow attrs to fallthrough', async () => {
|
||||
const click = jest.fn()
|
||||
const childUpdated = jest.fn()
|
||||
|
@ -8,10 +8,6 @@ import {
|
||||
serialize,
|
||||
serializeInner
|
||||
} from '@vue/runtime-test'
|
||||
import { mockWarn } from '@vue/shared'
|
||||
|
||||
mockWarn()
|
||||
|
||||
function toSpan(content: any) {
|
||||
if (typeof content === 'string') {
|
||||
return h('span', content.toString())
|
||||
|
@ -11,14 +11,12 @@ import {
|
||||
transformVNodeArgs
|
||||
} from '../src/vnode'
|
||||
import { Data } from '../src/component'
|
||||
import { ShapeFlags, PatchFlags, mockWarn } from '@vue/shared'
|
||||
import { ShapeFlags, PatchFlags } from '@vue/shared'
|
||||
import { h, reactive, isReactive } from '../src'
|
||||
import { createApp, nodeOps, serializeInner } from '@vue/runtime-test'
|
||||
import { setCurrentRenderingInstance } from '../src/componentRenderUtils'
|
||||
|
||||
describe('vnode', () => {
|
||||
mockWarn()
|
||||
|
||||
test('create with just tag', () => {
|
||||
const vnode = createVNode('p')
|
||||
expect(vnode.type).toBe('p')
|
||||
|
@ -1,10 +1,7 @@
|
||||
import { render, h, nodeOps } from '@vue/runtime-test'
|
||||
import { useCssModule } from '../../src/helpers/useCssModule'
|
||||
import { mockWarn } from '@vue/shared'
|
||||
|
||||
describe('useCssModule', () => {
|
||||
mockWarn()
|
||||
|
||||
function mountWithModule(modules: any, name?: string) {
|
||||
let res
|
||||
render(
|
||||
|
@ -1,10 +1,7 @@
|
||||
import { patchProp } from '../src/patchProp'
|
||||
import { render, h } from '../src'
|
||||
import { mockWarn } from '@vue/shared'
|
||||
|
||||
describe('runtime-dom: props patching', () => {
|
||||
mockWarn()
|
||||
|
||||
test('basic', () => {
|
||||
const el = document.createElement('div')
|
||||
patchProp(el, 'id', null, 'foo')
|
||||
|
@ -14,11 +14,8 @@ import {
|
||||
serialize,
|
||||
triggerEvent
|
||||
} from '../src'
|
||||
import { mockWarn } from '@vue/shared'
|
||||
|
||||
describe('test renderer', () => {
|
||||
mockWarn()
|
||||
|
||||
it('should work', () => {
|
||||
const root = nodeOps.createElement('div')
|
||||
render(
|
||||
|
@ -10,14 +10,11 @@ import {
|
||||
createTextVNode,
|
||||
createStaticVNode
|
||||
} from 'vue'
|
||||
import { escapeHtml, mockWarn } from '@vue/shared'
|
||||
import { escapeHtml } from '@vue/shared'
|
||||
import { renderToStream as _renderToStream } from '../src/renderToStream'
|
||||
import { Readable } from 'stream'
|
||||
import { ssrRenderSlot } from '../src/helpers/ssrRenderSlot'
|
||||
import { ssrRenderComponent } from '../src/helpers/ssrRenderComponent'
|
||||
|
||||
mockWarn()
|
||||
|
||||
const promisifyStream = (stream: Readable) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let result = ''
|
||||
|
@ -10,13 +10,10 @@ import {
|
||||
createTextVNode,
|
||||
createStaticVNode
|
||||
} from 'vue'
|
||||
import { escapeHtml, mockWarn } from '@vue/shared'
|
||||
import { escapeHtml } from '@vue/shared'
|
||||
import { renderToString } from '../src/renderToString'
|
||||
import { ssrRenderSlot, SSRSlot } from '../src/helpers/ssrRenderSlot'
|
||||
import { ssrRenderComponent } from '../src/helpers/ssrRenderComponent'
|
||||
|
||||
mockWarn()
|
||||
|
||||
describe('ssr: renderToString', () => {
|
||||
test('should apply app context', async () => {
|
||||
const app = createApp({
|
||||
|
@ -1,10 +1,7 @@
|
||||
import { createApp, h, Suspense } from 'vue'
|
||||
import { renderToString } from '../src/renderToString'
|
||||
import { mockWarn } from '@vue/shared'
|
||||
|
||||
describe('SSR Suspense', () => {
|
||||
mockWarn()
|
||||
|
||||
const ResolvingAsync = {
|
||||
async setup() {
|
||||
return () => h('div', 'async')
|
||||
|
@ -6,7 +6,6 @@ export * from './shapeFlags'
|
||||
export * from './slotFlags'
|
||||
export * from './globalsWhitelist'
|
||||
export * from './codeframe'
|
||||
export * from './mockWarn'
|
||||
export * from './normalizeProp'
|
||||
export * from './domTagConfig'
|
||||
export * from './domAttrConfig'
|
||||
|
@ -1,105 +0,0 @@
|
||||
declare global {
|
||||
namespace jest {
|
||||
interface Matchers<R, T> {
|
||||
toHaveBeenWarned(): R
|
||||
toHaveBeenWarnedLast(): R
|
||||
toHaveBeenWarnedTimes(n: number): R
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const mockError = () => mockWarn(true)
|
||||
|
||||
export function mockWarn(asError = false) {
|
||||
expect.extend({
|
||||
toHaveBeenWarned(received: string) {
|
||||
asserted.add(received)
|
||||
const passed = warn.mock.calls.some(
|
||||
args => args[0].indexOf(received) > -1
|
||||
)
|
||||
if (passed) {
|
||||
return {
|
||||
pass: true,
|
||||
message: () => `expected "${received}" not to have been warned.`
|
||||
}
|
||||
} else {
|
||||
const msgs = warn.mock.calls.map(args => args[0]).join('\n - ')
|
||||
return {
|
||||
pass: false,
|
||||
message: () =>
|
||||
`expected "${received}" to have been warned.\n\nActual messages:\n\n - ${msgs}`
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
toHaveBeenWarnedLast(received: string) {
|
||||
asserted.add(received)
|
||||
const passed =
|
||||
warn.mock.calls[warn.mock.calls.length - 1][0].indexOf(received) > -1
|
||||
if (passed) {
|
||||
return {
|
||||
pass: true,
|
||||
message: () => `expected "${received}" not to have been warned last.`
|
||||
}
|
||||
} else {
|
||||
const msgs = warn.mock.calls.map(args => args[0]).join('\n - ')
|
||||
return {
|
||||
pass: false,
|
||||
message: () =>
|
||||
`expected "${received}" to have been warned last.\n\nActual messages:\n\n - ${msgs}`
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
toHaveBeenWarnedTimes(received: string, n: number) {
|
||||
asserted.add(received)
|
||||
let found = 0
|
||||
warn.mock.calls.forEach(args => {
|
||||
if (args[0].indexOf(received) > -1) {
|
||||
found++
|
||||
}
|
||||
})
|
||||
|
||||
if (found === n) {
|
||||
return {
|
||||
pass: true,
|
||||
message: () =>
|
||||
`expected "${received}" to have been warned ${n} times.`
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
pass: false,
|
||||
message: () =>
|
||||
`expected "${received}" to have been warned ${n} times but got ${found}.`
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let warn: jest.SpyInstance
|
||||
const asserted: Set<string> = new Set()
|
||||
|
||||
beforeEach(() => {
|
||||
asserted.clear()
|
||||
warn = jest.spyOn(console, asError ? 'error' : 'warn')
|
||||
warn.mockImplementation(() => {})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
const assertedArray = Array.from(asserted)
|
||||
const nonAssertedWarnings = warn.mock.calls
|
||||
.map(args => args[0])
|
||||
.filter(received => {
|
||||
return !assertedArray.some(assertedMsg => {
|
||||
return received.indexOf(assertedMsg) > -1
|
||||
})
|
||||
})
|
||||
warn.mockRestore()
|
||||
if (nonAssertedWarnings.length) {
|
||||
nonAssertedWarnings.forEach(warning => {
|
||||
console.warn(warning)
|
||||
})
|
||||
throw new Error(`test case threw unexpected warnings.`)
|
||||
}
|
||||
})
|
||||
}
|
@ -1,10 +1,8 @@
|
||||
import { E2E_TIMEOUT, setupPuppeteer } from './e2eUtils'
|
||||
import path from 'path'
|
||||
import { mockWarn } from '@vue/shared'
|
||||
import { h, createApp, Transition } from 'vue'
|
||||
|
||||
describe('e2e: Transition', () => {
|
||||
mockWarn()
|
||||
const {
|
||||
page,
|
||||
html,
|
||||
|
@ -1,10 +1,8 @@
|
||||
import { E2E_TIMEOUT, setupPuppeteer } from './e2eUtils'
|
||||
import path from 'path'
|
||||
import { mockWarn } from '@vue/shared'
|
||||
import { createApp, ref } from 'vue'
|
||||
|
||||
describe('e2e: TransitionGroup', () => {
|
||||
mockWarn()
|
||||
const { page, html, nextFrame, timeout } = setupPuppeteer()
|
||||
const baseUrl = `file://${path.resolve(__dirname, './transition.html')}`
|
||||
|
||||
|
@ -1,9 +1,6 @@
|
||||
import { createApp, ref, nextTick } from '../src'
|
||||
import { mockWarn } from '@vue/shared'
|
||||
|
||||
describe('compiler + runtime integration', () => {
|
||||
mockWarn()
|
||||
|
||||
it('should support runtime template compilation', () => {
|
||||
const container = document.createElement('div')
|
||||
const App = {
|
||||
@ -55,7 +52,7 @@ describe('compiler + runtime integration', () => {
|
||||
expect(one.deactivated).toHaveBeenCalledTimes(0)
|
||||
expect(one.destroyed).toHaveBeenCalledTimes(0)
|
||||
|
||||
toggle.value = false;
|
||||
toggle.value = false
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(`<!--v-if-->`)
|
||||
expect(one.created).toHaveBeenCalledTimes(1)
|
||||
@ -64,7 +61,7 @@ describe('compiler + runtime integration', () => {
|
||||
expect(one.deactivated).toHaveBeenCalledTimes(1)
|
||||
expect(one.destroyed).toHaveBeenCalledTimes(0)
|
||||
|
||||
toggle.value = true;
|
||||
toggle.value = true
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(`one`)
|
||||
expect(one.created).toHaveBeenCalledTimes(1)
|
||||
|
88
scripts/setupJestEnv.ts
Normal file
88
scripts/setupJestEnv.ts
Normal file
@ -0,0 +1,88 @@
|
||||
expect.extend({
|
||||
toHaveBeenWarned(received: string) {
|
||||
asserted.add(received)
|
||||
const passed = warn.mock.calls.some(args => args[0].indexOf(received) > -1)
|
||||
if (passed) {
|
||||
return {
|
||||
pass: true,
|
||||
message: () => `expected "${received}" not to have been warned.`
|
||||
}
|
||||
} else {
|
||||
const msgs = warn.mock.calls.map(args => args[0]).join('\n - ')
|
||||
return {
|
||||
pass: false,
|
||||
message: () =>
|
||||
`expected "${received}" to have been warned.\n\nActual messages:\n\n - ${msgs}`
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
toHaveBeenWarnedLast(received: string) {
|
||||
asserted.add(received)
|
||||
const passed =
|
||||
warn.mock.calls[warn.mock.calls.length - 1][0].indexOf(received) > -1
|
||||
if (passed) {
|
||||
return {
|
||||
pass: true,
|
||||
message: () => `expected "${received}" not to have been warned last.`
|
||||
}
|
||||
} else {
|
||||
const msgs = warn.mock.calls.map(args => args[0]).join('\n - ')
|
||||
return {
|
||||
pass: false,
|
||||
message: () =>
|
||||
`expected "${received}" to have been warned last.\n\nActual messages:\n\n - ${msgs}`
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
toHaveBeenWarnedTimes(received: string, n: number) {
|
||||
asserted.add(received)
|
||||
let found = 0
|
||||
warn.mock.calls.forEach(args => {
|
||||
if (args[0].indexOf(received) > -1) {
|
||||
found++
|
||||
}
|
||||
})
|
||||
|
||||
if (found === n) {
|
||||
return {
|
||||
pass: true,
|
||||
message: () => `expected "${received}" to have been warned ${n} times.`
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
pass: false,
|
||||
message: () =>
|
||||
`expected "${received}" to have been warned ${n} times but got ${found}.`
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let warn: jest.SpyInstance
|
||||
const asserted: Set<string> = new Set()
|
||||
|
||||
beforeEach(() => {
|
||||
asserted.clear()
|
||||
warn = jest.spyOn(console, 'warn')
|
||||
warn.mockImplementation(() => {})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
const assertedArray = Array.from(asserted)
|
||||
const nonAssertedWarnings = warn.mock.calls
|
||||
.map(args => args[0])
|
||||
.filter(received => {
|
||||
return !assertedArray.some(assertedMsg => {
|
||||
return received.indexOf(assertedMsg) > -1
|
||||
})
|
||||
})
|
||||
warn.mockRestore()
|
||||
if (nonAssertedWarnings.length) {
|
||||
nonAssertedWarnings.forEach(warning => {
|
||||
console.warn(warning)
|
||||
})
|
||||
throw new Error(`test case threw unexpected warnings.`)
|
||||
}
|
||||
})
|
Loading…
Reference in New Issue
Block a user