-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #70 from simonsobs/dev
Fix a bug where `provide()` might be called outside of `setup()`
- Loading branch information
Showing
11 changed files
with
253 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
import { describe, expect, it } from "vitest"; | ||
import type { App, Ref } from "vue"; | ||
import { createApp, defineComponent, inject, nextTick, shallowRef, watch } from "vue"; | ||
|
||
import { injectionKey } from "../key"; | ||
import { useProvideConfigT } from "../provide-config"; | ||
|
||
type SetupFunction = () => Record<string, unknown>; | ||
|
||
interface WitSetupsOptions { | ||
setupParent: SetupFunction; | ||
setupChild: SetupFunction; | ||
} | ||
|
||
class DisposableApp implements Disposable { | ||
constructor(private app: App) {} | ||
|
||
[Symbol.dispose]() { | ||
this.app.unmount(); | ||
} | ||
} | ||
|
||
function withSetups(options: WitSetupsOptions): DisposableApp { | ||
const ParentComponent = defineComponent({ | ||
setup: options.setupParent, | ||
template: "<child-component />", | ||
}); | ||
|
||
const ChildComponent = defineComponent({ | ||
setup: options.setupChild, | ||
template: "<template />", | ||
}); | ||
|
||
const app = createApp(ParentComponent); | ||
app.component("ChildComponent", ChildComponent); | ||
app.mount(document.createElement("div")); | ||
return new DisposableApp(app); | ||
} | ||
|
||
describe("useProvideConfigT", () => { | ||
it("provides and updates config reactively when given a ShallowRef", async () => { | ||
const config = shallowRef({ key: "initial" }); | ||
let injectedConfig: Ref<{ key: string }> | undefined; | ||
|
||
using _ = withSetups({ | ||
setupParent() { | ||
useProvideConfigT(config); | ||
return {}; | ||
}, | ||
setupChild() { | ||
injectedConfig = inject(injectionKey); | ||
return {}; | ||
}, | ||
}); | ||
|
||
await nextTick(); | ||
expect(injectedConfig?.value).toEqual({ key: "initial" }); | ||
|
||
config.value = { key: "updated" }; | ||
await nextTick(); | ||
expect(injectedConfig?.value).toEqual({ key: "updated" }); | ||
|
||
config.value = { key: "final" }; | ||
await nextTick(); | ||
expect(injectedConfig?.value).toEqual({ key: "final" }); | ||
}); | ||
|
||
it("works with non-ref values", async () => { | ||
let injectedConfig: Ref<{ key: string }> | undefined; | ||
|
||
using _ = withSetups({ | ||
setupParent() { | ||
useProvideConfigT({ key: "static" }); | ||
return {}; | ||
}, | ||
setupChild() { | ||
injectedConfig = inject(injectionKey); | ||
return {}; | ||
}, | ||
}); | ||
|
||
await nextTick(); | ||
expect(injectedConfig?.value).toEqual({ key: "static" }); | ||
}); | ||
|
||
it("returns the provided ShallowRef", () => { | ||
let returnedConfig: Ref<{ key: string }> | undefined; | ||
|
||
using _ = withSetups({ | ||
setupParent() { | ||
const result = useProvideConfigT({ key: "test" }); | ||
returnedConfig = result.config; | ||
return {}; | ||
}, | ||
setupChild() { | ||
return {}; | ||
}, | ||
}); | ||
|
||
expect(returnedConfig?.value).toEqual({ key: "test" }); | ||
}); | ||
|
||
it("maintains shallow reactivity", async () => { | ||
const config = shallowRef({ nested: { value: "initial" } }); | ||
let injectedConfig: Ref<{ nested: { value: string } }> | undefined; | ||
let watchCount = 0; | ||
|
||
using _ = withSetups({ | ||
setupParent() { | ||
useProvideConfigT(config); | ||
return {}; | ||
}, | ||
setupChild() { | ||
injectedConfig = inject(injectionKey); | ||
watch( | ||
() => injectedConfig?.value, | ||
() => { | ||
watchCount++; | ||
}, | ||
{ deep: true } | ||
); | ||
return {}; | ||
}, | ||
}); | ||
|
||
await nextTick(); | ||
expect(injectedConfig?.value.nested.value).toBe("initial"); | ||
|
||
config.value.nested.value = "updated"; | ||
await nextTick(); | ||
expect(injectedConfig?.value.nested.value).toBe("updated"); | ||
expect(watchCount).toBe(0); // Should not trigger watch due to shallow reactivity | ||
|
||
config.value = { nested: { value: "new object" } }; | ||
await nextTick(); | ||
expect(injectedConfig?.value.nested.value).toBe("new object"); | ||
expect(watchCount).toBe(1); // Should trigger watch | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,37 +1,9 @@ | ||
import { ref, provide, watchEffect, toValue } from "vue"; | ||
import type { MaybeRefOrGetter, InjectionKey, Ref } from "vue"; | ||
|
||
import type { InjectionKey, Ref, ShallowRef } from "vue"; | ||
import { isRef, provide, shallowRef } from "vue"; | ||
import { injectionKey } from "./key"; | ||
/** | ||
* Provides a configuration of type `T` to child components. | ||
* | ||
* This function creates a reactive reference to the config and provides it | ||
* to child components using Vue's provide/inject system. | ||
* | ||
* @template T The type of the configuration object | ||
* @param The configuration, which can be: | ||
* - A value of type T | ||
* - A Ref<T | null> | ||
* - A getter function returning T or null | ||
* | ||
* @remarks | ||
* In child components, use `useConfig` to retrieve this provided config. | ||
* The config will only be provided once it has a non-null value. | ||
* Subsequent updates to the config (including to undefined) will be reflected | ||
* in the provided reference, but will not trigger a new provide. | ||
*/ | ||
export function useProvideConfigT<T>( | ||
config: Exclude<MaybeRefOrGetter<T | null>, null> | ||
) { | ||
const configRef = ref<T>() as Ref<T>; | ||
let provided = false; | ||
|
||
watchEffect(() => { | ||
const value = toValue(config); | ||
if (value === null) return; | ||
configRef.value = value; | ||
if (provided) return; | ||
provide(injectionKey as InjectionKey<Ref<T>>, configRef); | ||
provided = true; | ||
}); | ||
export function useProvideConfigT<T>(config: T | ShallowRef<T>) { | ||
const configRef = isRef(config) ? config : shallowRef(config); | ||
provide(injectionKey as InjectionKey<Ref<T>>, configRef); | ||
return { config: configRef }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.