use super::{Binding, Emitter};
use crate::ir::Ir;
pub struct ReactEmitter {
pub core: String,
pub binding: Binding,
}
const TEMPLATE: &str = r#"// AUTO-GENERATED by stele — do not edit.
// Reactive React / React Native bindings over the generated copy.
// Changing the locale via setLocale re-renders every __HOOK__() consumer.
import {
createContext,
createElement,
useContext,
useMemo,
useState,
type ReactNode,
} from "react";
import { __FACTORY__, type __TY__, type Locale } from "__CORE__";
type __TY__ContextValue = {
__FIELD__: __TY__;
locale: Locale;
setLocale: (locale: Locale) => void;
};
const __TY__Context = createContext<__TY__ContextValue | null>(null);
export function __PROVIDER__(props: { locale: Locale; children: ReactNode }) {
const [locale, setLocale] = useState<Locale>(props.locale);
const __FIELD__ = useMemo(() => __FACTORY__(locale), [locale]);
const value = useMemo(() => ({ __FIELD__, locale, setLocale }), [__FIELD__, locale]);
return createElement(__TY__Context.Provider, { value }, props.children);
}
export function __HOOK__(): __TY__ {
const ctx = useContext(__TY__Context);
if (ctx === null) {
throw new Error("__HOOK__ must be used within a <__PROVIDER__>");
}
return ctx.__FIELD__;
}
export function useLocale(): [Locale, (locale: Locale) => void] {
const ctx = useContext(__TY__Context);
if (ctx === null) {
throw new Error("useLocale must be used within a <__PROVIDER__>");
}
return [ctx.locale, ctx.setLocale];
}
"#;
impl Emitter for ReactEmitter {
fn emit(&self, _ir: &Ir) -> String {
TEMPLATE
.replace("__CORE__", &self.core)
.replace("__FACTORY__", &self.binding.factory())
.replace("__PROVIDER__", &self.binding.provider())
.replace("__HOOK__", &self.binding.hook())
.replace("__FIELD__", &self.binding.field)
.replace("__TY__", &self.binding.ty)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::BTreeMap;
fn empty_ir() -> Ir {
Ir {
canonical: "en".into(),
locales: vec!["en".into()],
messages: vec![],
plural_rules: BTreeMap::new(),
}
}
#[test]
fn substitutes_core_and_exports_default_stele_bindings() {
let out = ReactEmitter {
core: "./my-copy".into(),
binding: Binding::new("stele"),
}
.emit(&empty_ir());
assert!(out.contains("from \"./my-copy\""));
assert!(out.contains("import { createStele, type Stele, type Locale }"));
assert!(out.contains("export function SteleProvider"));
assert!(out.contains("export function useStele(): Stele"));
assert!(out.contains("export function useLocale"));
assert!(out.contains("useStele must be used within a <SteleProvider>"));
assert!(!out.contains("__"));
}
#[test]
fn binding_renames_the_whole_surface() {
let out = ReactEmitter {
core: "./copy.gen".into(),
binding: Binding::new("copy"),
}
.emit(&empty_ir());
assert!(out.contains("export function CopyProvider"));
assert!(out.contains("export function useCopy(): Copy"));
assert!(out.contains("import { createCopy, type Copy, type Locale }"));
}
}