isPromise,
} from '@vue/shared'
import {
+ type ComponentInternalInstance,
type SetupContext,
createSetupContext,
- getCurrentInstance,
+ getCurrentGenericInstance,
setCurrentInstance,
unsetCurrentInstance,
} from './component'
return null as any
}
+// TODO return type for Vapor components
export function useSlots(): SetupContext['slots'] {
return getContext().slots
}
}
function getContext(): SetupContext {
- const i = getCurrentInstance()!
+ const i = getCurrentGenericInstance()!
if (__DEV__ && !i) {
warn(`useContext() called without active instance.`)
}
- return i.setupContext || (i.setupContext = createSetupContext(i))
+ if (i.vapor) {
+ return i as any // vapor instance act as its own setup context
+ } else {
+ const ii = i as ComponentInternalInstance
+ return ii.setupContext || (ii.setupContext = createSetupContext(ii))
+ }
}
/**
* @internal
*/
export function withAsyncContext(getAwaitable: () => any): [any, () => void] {
- const ctx = getCurrentInstance()!
+ const ctx = getCurrentGenericInstance()!
if (__DEV__ && !ctx) {
warn(
`withAsyncContext called without active current instance. ` +
import { createComponent, defineVaporComponent, template } from '../src'
-import { ref, useAttrs, useSlots } from '@vue/runtime-dom'
+import {
+ currentInstance,
+ onMounted,
+ ref,
+ useAttrs,
+ useSlots,
+ withAsyncContext,
+} from '@vue/runtime-dom'
import { makeRender } from './_utils'
import type { VaporComponentInstance } from '../src/component'
const define = makeRender<any>()
-describe.todo('SFC <script setup> helpers', () => {
- test.todo('should warn runtime usage', () => {})
-
+describe('SFC <script setup> helpers', () => {
test('useSlots / useAttrs (no args)', () => {
let slots: VaporComponentInstance['slots'] | undefined
let attrs: VaporComponentInstance['attrs'] | undefined
expect(attrs).toBe(ctx!.attrs)
})
- describe.todo('mergeDefaults', () => {
- test.todo('object syntax', () => {})
- test.todo('array syntax', () => {})
- test.todo('merging with skipFactory', () => {})
- test.todo('should warn missing', () => {})
- })
+ describe.todo('withAsyncContext', () => {
+ test('basic', async () => {
+ const spy = vi.fn()
- describe('mergeModels', () => {
- test.todo('array syntax', () => {})
- test.todo('object syntax', () => {})
- test.todo('overwrite', () => {})
- })
+ let beforeInstance: VaporComponentInstance | null = null
+ let afterInstance: VaporComponentInstance | null = null
+ let resolve: (msg: string) => void
- test.todo('createPropsRestProxy', () => {})
+ const Comp = defineVaporComponent({
+ async setup() {
+ let __temp: any, __restore: any
+
+ beforeInstance = currentInstance as VaporComponentInstance
+
+ const msg =
+ (([__temp, __restore] = withAsyncContext(
+ () =>
+ new Promise(r => {
+ resolve = r
+ }),
+ )),
+ (__temp = await __temp),
+ __restore(),
+ __temp)
+
+ // register the lifecycle after an await statement
+ onMounted(spy)
+ afterInstance = currentInstance as VaporComponentInstance
+ return document.createTextNode(msg)
+ },
+ })
+
+ const { html } = define(Comp).render()
+
+ expect(spy).not.toHaveBeenCalled()
+ resolve!('hello')
+ // wait a macro task tick for all micro ticks to resolve
+ await new Promise(r => setTimeout(r))
+ // mount hook should have been called
+ expect(spy).toHaveBeenCalled()
+ // should retain same instance before/after the await call
+ expect(beforeInstance).toBe(afterInstance)
+ expect(html()).toBe('hello')
+ })
- describe.todo('withAsyncContext', () => {
- test.todo('basic', async () => {})
test.todo('error handling', async () => {})
test.todo('should not leak instance on multiple awaits', async () => {})
test.todo('should not leak on multiple awaits + error', async () => {})
watch,
watchEffect,
} from '@vue/runtime-dom'
-import { createComponent, defineVaporComponent, renderEffect } from '../src'
+import {
+ createComponent,
+ createIf,
+ createTemplateRefSetter,
+ defineVaporComponent,
+ renderEffect,
+ template,
+} from '../src'
import { makeRender } from './_utils'
import type { VaporComponentInstance } from '../src/component'
+import type { RefEl } from '../src/apiTemplateRef'
const define = makeRender()
// only need to port test cases related to in-component usage
describe('apiWatch', () => {
// #7030
- it.todo(
- // need if support
- 'should not fire on child component unmount w/ flush: pre',
- async () => {
- const visible = ref(true)
- const cb = vi.fn()
- const Parent = defineVaporComponent({
- props: ['visible'],
- setup() {
- // @ts-expect-error
- return visible.value ? h(Comp) : null
- },
- })
- const Comp = {
- setup() {
- watch(visible, cb, { flush: 'pre' })
- return []
- },
- }
- define(Parent).render({
- visible: () => visible.value,
- })
- expect(cb).not.toHaveBeenCalled()
- visible.value = false
- await nextTick()
- expect(cb).not.toHaveBeenCalled()
- },
- )
+ it(// need if support
+ 'should not fire on child component unmount w/ flush: pre', async () => {
+ const visible = ref(true)
+ const cb = vi.fn()
+ const Parent = defineVaporComponent({
+ props: ['visible'],
+ setup() {
+ return createIf(
+ () => visible.value,
+ () => createComponent(Comp),
+ )
+ },
+ })
+ const Comp = {
+ setup() {
+ watch(visible, cb, { flush: 'pre' })
+ return []
+ },
+ }
+ define(Parent).render({
+ visible: () => visible.value,
+ })
+ expect(cb).not.toHaveBeenCalled()
+ visible.value = false
+ await nextTick()
+ expect(cb).not.toHaveBeenCalled()
+ })
// #7030
it('flush: pre watcher in child component should not fire before parent update', async () => {
})
// #1852
- it.todo(
- // need if + templateRef
- 'flush: post watcher should fire after template refs updated',
- async () => {
- const toggle = ref(false)
- let dom: Element | null = null
+ it('flush: post watcher should fire after template refs updated', async () => {
+ const toggle = ref(false)
+ let dom: Element | null = null
+
+ const App = {
+ setup() {
+ const domRef = ref<Element | null>(null)
- const App = {
- setup() {
- const domRef = ref<Element | null>(null)
+ watch(
+ toggle,
+ () => {
+ dom = domRef.value
+ },
+ { flush: 'post' },
+ )
- watch(
- toggle,
- () => {
- dom = domRef.value
- },
- { flush: 'post' },
- )
+ const setRef = createTemplateRefSetter()
+ return createIf(
+ () => toggle.value,
+ () => {
+ const n = template('<p>')()
+ setRef(n as RefEl, domRef)
+ return n
+ },
+ )
+ },
+ }
- return () => {
- // @ts-expect-error
- return toggle.value ? h('p', { ref: domRef }) : null
- }
- },
- }
-
- // @ts-expect-error
- render(h(App), nodeOps.createElement('div'))
- expect(dom).toBe(null)
-
- toggle.value = true
- await nextTick()
- expect(dom!.tagName).toBe('P')
- },
- )
+ define(App).render()
+ expect(dom).toBe(null)
+
+ toggle.value = true
+ await nextTick()
+ expect(dom!.tagName).toBe('P')
+ })
test('should not leak `this.proxy` to setup()', () => {
const source = vi.fn()
expect(instance.slots).toHaveProperty('two')
})
- test.todo('should work with createFlorSlots', async () => {
+ test('should work with createFlorSlots', async () => {
const loop = ref([1, 2, 3])
let instance: any
return createComponent(Child, null, {
$: [
() =>
- // @ts-expect-error
createForSlots(loop.value, (item, i) => ({
name: item,
fn: () => template(item + i)(),
})
// compiled output of v-for + template ref
- test.todo('ref in v-for', async () => {
+ test('ref in v-for', async () => {
const show = ref(true)
const list = reactive([1, 2, 3])
const listRefs = ref([])
expect(mapRefs()).toMatchObject(['2', '3', '4'])
})
- test.todo('named ref in v-for', async () => {
+ test('named ref in v-for', async () => {
const show = ref(true)
const list = reactive([1, 2, 3])
const listRefs = ref([])
})
// #6697 v-for ref behaves differently under production and development
- test.todo(
- 'named ref in v-for , should be responsive when rendering',
- async () => {
- const list = ref([1, 2, 3])
- const listRefs = ref([])
-
- const t0 = template('<div><div></div><ul></ul></div>')
- const t1 = template('<li></li>')
- const { render } = define({
- setup() {
- return { listRefs }
- },
- render() {
- const n0 = t0()
- const n1 = n0.firstChild
- const n2 = n1!.nextSibling!
- const n3 = createFor(
- () => list.value,
- state => {
- const n4 = t1()
- createTemplateRefSetter()(
- n4 as Element,
- 'listRefs',
- undefined,
- true,
- )
- renderEffect(() => {
- const [item] = state
- setText(n4, item)
- })
- return n4
- },
- )
- insert(n3, n2 as unknown as ParentNode)
- renderEffect(() => {
- setText(n1!, String(listRefs.value))
- })
- return n0
- },
- })
-
- const { host } = render()
-
- await nextTick()
- expect(String(listRefs.value)).toBe(
- '[object HTMLLIElement],[object HTMLLIElement],[object HTMLLIElement]',
- )
- expect(host.innerHTML).toBe(
- '<div><div>[object HTMLLIElement],[object HTMLLIElement],[object HTMLLIElement]</div><ul><li>1</li><li>2</li><li>3</li><!--for--></ul></div>',
- )
-
- list.value.splice(0, 1)
- await nextTick()
- expect(String(listRefs.value)).toBe(
- '[object HTMLLIElement],[object HTMLLIElement]',
- )
- expect(host.innerHTML).toBe(
- '<div><div>[object HTMLLIElement],[object HTMLLIElement]</div><ul><li>2</li><li>3</li><!--for--></ul></div>',
- )
- },
- )
+ test('named ref in v-for , should be responsive when rendering', async () => {
+ const list = ref([1, 2, 3])
+ const listRefs = ref([])
+
+ const t0 = template('<div><div></div><ul></ul></div>')
+ const t1 = template('<li></li>')
+ const { render } = define({
+ setup() {
+ return { listRefs }
+ },
+ render() {
+ const n0 = t0()
+ const n1 = n0.firstChild
+ const n2 = n1!.nextSibling!
+ const n3 = createFor(
+ () => list.value,
+ state => {
+ const n4 = t1()
+ createTemplateRefSetter()(
+ n4 as Element,
+ 'listRefs',
+ undefined,
+ true,
+ )
+ renderEffect(() => {
+ const [item] = state
+ setText(n4, item)
+ })
+ return n4
+ },
+ )
+ insert(n3, n2 as unknown as ParentNode)
+ renderEffect(() => {
+ setText(n1!, String(listRefs.value))
+ })
+ return n0
+ },
+ })
+
+ const { host } = render()
+
+ await nextTick()
+ expect(String(listRefs.value)).toBe(
+ '[object HTMLLIElement],[object HTMLLIElement],[object HTMLLIElement]',
+ )
+ expect(host.innerHTML).toBe(
+ '<div><div>[object HTMLLIElement],[object HTMLLIElement],[object HTMLLIElement]</div><ul><li>1</li><li>2</li><li>3</li><!--for--></ul></div>',
+ )
+
+ list.value.splice(0, 1)
+ await nextTick()
+ expect(String(listRefs.value)).toBe(
+ '[object HTMLLIElement],[object HTMLLIElement]',
+ )
+ expect(host.innerHTML).toBe(
+ '<div><div>[object HTMLLIElement],[object HTMLLIElement]</div><ul><li>2</li><li>3</li><!--for--></ul></div>',
+ )
+ })
test('string ref inside slots', () => {
const { component: Child } = define({
expect(fn).toHaveBeenCalledWith(err, 'render function')
})
- test.todo('in function ref', () => {
+ test('in function ref', () => {
const err = new Error('foo')
const ref = () => {
throw err
}
export function createForSlots(
- source: any[] | Record<any, any> | number | Set<any> | Map<any, any>,
+ source: Source,
getSlot: (item: any, key: any, index?: number) => DynamicSlot,
): DynamicSlot[] {
const sourceLength = getLength(source)
isVaporComponent,
} from './component'
import {
+ ErrorCodes,
type SchedulerJob,
callWithErrorHandling,
queuePostFlushCb,
if (isFunction(ref)) {
const invokeRefSetter = (value?: Element | Record<string, any>) => {
- callWithErrorHandling(
- ref,
- currentInstance,
- // @ts-expect-error
- null,
- [value, refs],
- )
+ callWithErrorHandling(ref, currentInstance, ErrorCodes.FUNCTION_REF, [
+ value,
+ refs,
+ ])
}
invokeRefSetter(refValue)
}
},
ownKeys(target) {
- const keys = Object.keys(target)
+ let keys = Object.keys(target)
const dynamicSources = target.$
if (dynamicSources) {
+ keys = keys.filter(k => k !== '$')
for (const source of dynamicSources) {
if (isFunction(source)) {
const slot = source()
if (isArray(slot)) {
- for (const s of slot) keys.push(s.name)
+ for (const s of slot) keys.push(String(s.name))
} else {
- keys.push(slot.name)
+ keys.push(String(slot.name))
}
} else {
keys.push(...Object.keys(source))
if (slot) {
if (isArray(slot)) {
for (const s of slot) {
- if (s.name === key) return s.fn
+ if (String(s.name) === key) return s.fn
}
- } else if (slot.name === key) {
+ } else if (String(slot.name) === key) {
return slot.fn
}
}
return fragment
}
-
-// TODO
-export function createForSlots(): any {}
export { insert, prepend, remove } from './block'
export { createComponent, createComponentWithFallback } from './component'
export { renderEffect } from './renderEffect'
-export { createSlot, createForSlots } from './componentSlots'
+export { createSlot } from './componentSlots'
export { template, children, next } from './dom/template'
export { createTextNode } from './dom/node'
export {
} from './dom/prop'
export { on, delegate, delegateEvents, setDynamicEvents } from './dom/event'
export { createIf } from './apiCreateIf'
-export { createFor } from './apiCreateFor'
+export { createFor, createForSlots } from './apiCreateFor'
export { createTemplateRefSetter } from './apiTemplateRef'