sofie_engine/client/
mod.rs1mod emit;
4mod script;
5mod tree;
6
7use std::fmt;
8
9use crate::{Components, Template, TemplateError};
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum Client {
13 Svelte,
14}
15
16impl Client {
17 #[must_use]
19 pub fn name(self) -> &'static str {
20 match self {
21 Self::Svelte => "svelte",
22 }
23 }
24
25 #[must_use]
26 pub fn extension(self) -> &'static str {
27 match self {
28 Self::Svelte => "svelte",
29 }
30 }
31}
32
33impl fmt::Display for Client {
34 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
35 formatter.write_str(match self {
36 Self::Svelte => "Svelte",
37 })
38 }
39}
40
41pub fn compile_client(
43 name: &str,
44 source: &str,
45 components: &Components,
46 client: Client,
47) -> Result<String, TemplateError> {
48 let template = Template::parse_with(name, source, components)?;
49 tree::tree(&template.ops)
50 .and_then(|nodes| emit::Emitter::new().screen(&nodes))
51 .map_err(|message| {
52 TemplateError::unlocated(name, format!("compiling for {client}: {message}"))
53 })
54}
55
56#[cfg(test)]
57mod tests {
58 use super::{Client, compile_client};
59 use crate::Components;
60
61 const SOURCE: &str = "<Section title=\"Tom & Jerry\">\
62 <ul class=\"list {data.kind}\">{#each data.items as item, index}\
63 <li><a href={item.url}>{item.name}</a></li>{/each}</ul>\
64 {#if data.empty}<p>None {\"{x}\"}</p>{:else}<input required>{/if}\
65 <Table id=\"t\" of={data.rows} columns=\"name\" as=\"row\">\
66 <Action post=\"/r/{row.id}\" label=\"Go\" /></Table></Section>";
67
68 fn compiled(client: Client) -> String {
69 compile_client("screen", SOURCE, &Components::default(), client).unwrap()
70 }
71
72 #[test]
73 fn svelte_keeps_markup_and_writes_blocks_and_snippets() {
74 let svelte = compiled(Client::Svelte);
75
76 for expected in [
77 "props={{\"title\": S.text(\"Tom & Jerry\")}}",
78 "<ul class=\"list {S.text(data?.kind)}\">",
79 "{#each S.items(data?.items) as s0, s1}<li><a href=\"{S.url(S.text(s0?.url))}\">",
80 "{S.text(s0?.name)}</a></li>{/each}",
81 "{#if S.truthy(data?.empty)}<p>None {S.text(\"{x}\")}</p>{:else}<input required=\"\">{/if}",
82 "{#snippet row(s0)}<Catalog {view} name=\"Action\" props={{\"post\": \
83 S.join([\"/r/\", S.urlPart(s0?.id)]), \"label\": S.text(\"Go\")}} />{/snippet}",
84 ] {
85 assert!(svelte.contains(expected), "{expected}\n{svelte}");
86 }
87 }
88
89 #[test]
90 fn an_action_passes_its_posted_fields_as_values() {
91 let source = "{#each data.slots as slot}<Action post=\"/slots/book\" label=\"Book\" \
92 with={{slot: slot.id, seats: 2}} />{/each}";
93
94 let svelte =
95 compile_client("slots", source, &Components::default(), Client::Svelte).unwrap();
96
97 assert!(
98 svelte.contains("\"with\": {\"slot\": s0?.id, \"seats\": 2}"),
99 "{svelte}"
100 );
101 }
102
103 #[test]
104 fn a_svelte_screen_s_title_names_the_page_through_its_head() {
105 let svelte = compile_client(
106 "order",
107 "<title>Order {data.id}</title><p>x</p>",
108 &Components::default(),
109 Client::Svelte,
110 )
111 .unwrap();
112
113 assert!(
114 svelte.contains(
115 "<svelte:head><title>Order {S.text(data?.id)}</title></svelte:head><p>x</p>"
116 ),
117 "{svelte}"
118 );
119 }
120
121 #[test]
122 fn written_pages_stay_in_html_areas() {
123 let error = compile_client(
124 "page",
125 "<Markdown text={data.body} />",
126 &Components::default(),
127 Client::Svelte,
128 )
129 .unwrap_err();
130
131 assert!(error.message.contains("only in HTML areas"), "{error}");
132 }
133
134 #[test]
135 fn page_script_attributes_are_kept_for_the_client_runtime() {
136 let svelte = compile_client(
137 "passkeys",
138 "<button type=\"button\" data-sofie-passkey=\"create\" data-options={data.options} \
139 data-post=\"passkeys\">Add</button><form method=\"post\" action={data.action} \
140 data-sofie-autosubmit=\"true\"></form>",
141 &Components::default(),
142 Client::Svelte,
143 )
144 .unwrap();
145
146 for expected in [
147 "<button type=\"button\" data-sofie-passkey=\"create\" \
148 data-options=\"{S.text(data?.options)}\" data-post=\"passkeys\">Add</button>",
149 "data-sofie-autosubmit=\"true\"",
150 ] {
151 assert!(svelte.contains(expected), "{expected}\n{svelte}");
152 }
153 }
154}