Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions packages/solid/src/reactive/signal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1130,10 +1130,10 @@ export function registerGraph(value: SourceMapValue): void {
export type ContextProviderComponent<T> = FlowComponent<{ value: T }>;

// Context API
export interface Context<T> {
export interface Context<T, D = T> {
id: symbol;
Provider: ContextProviderComponent<T>;
defaultValue: T;
defaultValue: D;
}

/**
Expand All @@ -1158,7 +1158,7 @@ export interface Context<T> {
export function createContext<T>(
defaultValue?: undefined,
options?: EffectOptions
): Context<T | undefined>;
): Context<T, undefined>;
export function createContext<T>(defaultValue: T, options?: EffectOptions): Context<T>;
export function createContext<T>(
defaultValue?: T,
Expand All @@ -1176,7 +1176,7 @@ export function createContext<T>(
*
* @description https://www.solidjs.com/docs/latest/api#usecontext
*/
export function useContext<T>(context: Context<T>): T {
export function useContext<T, D>(context: Context<T, D>): D extends undefined ? T | undefined : T {
return Owner && Owner.context && Owner.context[context.id] !== undefined
? Owner.context[context.id]
: context.defaultValue;
Expand Down Expand Up @@ -1215,7 +1215,7 @@ export type SuspenseContextType = {
resolved?: boolean;
};

type SuspenseContext = Context<SuspenseContextType | undefined> & {
type SuspenseContext = Context<SuspenseContextType, undefined> & {
active?(): boolean;
increment?(): void;
decrement?(): void;
Expand All @@ -1224,7 +1224,7 @@ type SuspenseContext = Context<SuspenseContextType | undefined> & {
let SuspenseContext: SuspenseContext;

export function getSuspenseContext() {
return SuspenseContext || (SuspenseContext = createContext<SuspenseContextType | undefined>());
return SuspenseContext || (SuspenseContext = createContext<SuspenseContextType>());
}

// Interop
Expand Down
85 changes: 72 additions & 13 deletions packages/solid/web/test/context.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ describe("Testing Context", () => {
</Show>
);
};
const div = document.createElement("div");

it("should create context properly", () => {
expect(ThemeContext.id).toBeDefined();
expect(ThemeContext.defaultValue).toBe("light");
});

it("should work with single provider child", () => {
const div = document.createElement("div");
render(
() => (
<ThemeContext.Provider value="dark">
Expand All @@ -35,11 +36,11 @@ describe("Testing Context", () => {
),
div
);
expect((div.firstChild as HTMLDivElement).innerHTML).toBe("dark");
div.innerHTML = "";
expect(div.children[0].innerHTML).toBe("dark");
});

it("should work with single conditional provider child", () => {
const div = document.createElement("div");
render(
() => (
<ThemeContext.Provider value="dark">
Expand All @@ -48,11 +49,11 @@ describe("Testing Context", () => {
),
div
);
expect((div.firstChild as HTMLDivElement).innerHTML).toBe("dark");
div.innerHTML = "";
expect(div.children[0].innerHTML).toBe("dark");
});

it("should work with multi provider child", () => {
const div = document.createElement("div");
render(
() => (
<ThemeContext.Provider value="dark">
Expand All @@ -62,11 +63,11 @@ describe("Testing Context", () => {
),
div
);
expect((div.firstChild!.nextSibling! as HTMLDivElement).innerHTML).toBe("dark");
div.innerHTML = "";
expect(div.children[1].innerHTML).toBe("dark");
});

it("should work with multi conditional provider child", () => {
const div = document.createElement("div");
render(
() => (
<ThemeContext.Provider value="dark">
Expand All @@ -76,11 +77,11 @@ describe("Testing Context", () => {
),
div
);
expect((div.firstChild!.nextSibling! as HTMLDivElement).innerHTML).toBe("dark");
div.innerHTML = "";
expect(div.children[1].innerHTML).toBe("dark");
});

it("should work with dynamic multi provider child", () => {
const div = document.createElement("div");
const child = () => <Component />;
render(
() => (
Expand All @@ -91,11 +92,11 @@ describe("Testing Context", () => {
),
div
);
expect((div.firstChild!.nextSibling! as HTMLDivElement).innerHTML).toBe("dark");
div.innerHTML = "";
expect(div.children[1].innerHTML).toBe("dark");
});

it("should work with dynamic multi conditional provider child", () => {
const div = document.createElement("div");
const child = () => <CondComponent />;
render(
() => (
Expand All @@ -106,7 +107,65 @@ describe("Testing Context", () => {
),
div
);
expect((div.firstChild!.nextSibling! as HTMLDivElement).innerHTML).toBe("dark");
div.innerHTML = "";
expect(div.children[1].innerHTML).toBe("dark");
});

const ThemeContextWithUndefined = createContext<string | undefined>("light");
const ComponentWithUndefined = () => {
const theme = useContext(ThemeContextWithUndefined);
// ?? 'undefined' will never get reached
return <div>{theme ?? "undefined"}</div>;
};

it("should override when nesting", () => {
const div = document.createElement("div");
render(
() => (
<>
<ComponentWithUndefined />
<ThemeContextWithUndefined.Provider value="dark">
<ComponentWithUndefined />
<ThemeContextWithUndefined.Provider value="darker">
<ComponentWithUndefined />
<ThemeContextWithUndefined.Provider value={undefined}>
<ComponentWithUndefined />
</ThemeContextWithUndefined.Provider>
</ThemeContextWithUndefined.Provider>
</ThemeContextWithUndefined.Provider>
</>
),
div
);
expect(div.children[0].innerHTML!).toBe("light");
expect(div.children[1].innerHTML!).toBe("dark");
expect(div.children[2].innerHTML!).toBe("darker");
expect(div.children[3].innerHTML!).toBe("light");
});

const ThemeContextWithoutDefault = createContext<string | undefined>();
const ComponentWithoutDefault = () => {
const theme = useContext(ThemeContextWithoutDefault);
return <div>{theme ?? "no-default"}</div>;
};

it("should work with no default provided", () => {
const div = document.createElement("div");
render(
() => (
<>
<ComponentWithoutDefault />
<ThemeContextWithoutDefault.Provider value="dark">
<ComponentWithoutDefault />
<ThemeContextWithoutDefault.Provider value={undefined}>
<ComponentWithoutDefault />
</ThemeContextWithoutDefault.Provider>
</ThemeContextWithoutDefault.Provider>
</>
),
div
);
expect(div.children[0].innerHTML!).toBe("no-default");
expect(div.children[1].innerHTML!).toBe("dark");
expect(div.children[2].innerHTML!).toBe("no-default");
});
});