1use std::ops::{Deref, DerefMut};
3use std::rc::Rc;
4
5use super::{Key, VNode};
6use crate::html::ImplicitClone;
7
8#[derive(Clone, Copy, Debug, PartialEq)]
9enum FullyKeyedState {
10 KnownFullyKeyed,
11 KnownMissingKeys,
12 Unknown,
13}
14
15#[derive(Clone, Debug)]
17pub struct VList {
18 pub(crate) children: Option<Rc<Vec<VNode>>>,
20
21 fully_keyed: FullyKeyedState,
23
24 pub key: Option<Key>,
25}
26
27impl ImplicitClone for VList {}
28
29impl PartialEq for VList {
30 fn eq(&self, other: &Self) -> bool {
31 self.key == other.key && self.children == other.children
32 }
33}
34
35impl Default for VList {
36 fn default() -> Self {
37 Self::new()
38 }
39}
40
41impl Deref for VList {
42 type Target = Vec<VNode>;
43
44 fn deref(&self) -> &Self::Target {
45 match self.children {
46 Some(ref m) => m,
47 None => {
48 const EMPTY: &Vec<VNode> = &Vec::new();
50 EMPTY
51 }
52 }
53 }
54}
55
56impl DerefMut for VList {
57 fn deref_mut(&mut self) -> &mut Self::Target {
58 self.fully_keyed = FullyKeyedState::Unknown;
59 self.children_mut()
60 }
61}
62
63impl VList {
64 pub const fn new() -> Self {
66 Self {
67 children: None,
68 key: None,
69 fully_keyed: FullyKeyedState::KnownFullyKeyed,
70 }
71 }
72
73 pub fn with_children(children: Vec<VNode>, key: Option<Key>) -> Self {
75 let mut vlist = VList {
76 fully_keyed: FullyKeyedState::Unknown,
77 children: Some(Rc::new(children)),
78 key,
79 };
80 vlist.recheck_fully_keyed();
81 vlist
82 }
83
84 fn children_mut(&mut self) -> &mut Vec<VNode> {
88 loop {
89 match self.children {
90 Some(ref mut m) => return Rc::make_mut(m),
91 None => {
92 self.children = Some(Rc::new(Vec::new()));
93 }
94 }
95 }
96 }
97
98 pub fn add_child(&mut self, child: VNode) {
100 if self.fully_keyed == FullyKeyedState::KnownFullyKeyed && !child.has_key() {
101 self.fully_keyed = FullyKeyedState::KnownMissingKeys;
102 }
103 self.children_mut().push(child);
104 }
105
106 pub fn add_children(&mut self, children: impl IntoIterator<Item = VNode>) {
108 let it = children.into_iter();
109 let bound = it.size_hint();
110 self.children_mut().reserve(bound.1.unwrap_or(bound.0));
111 for ch in it {
112 self.add_child(ch);
113 }
114 }
115
116 pub fn recheck_fully_keyed(&mut self) {
121 self.fully_keyed = if self.fully_keyed() {
122 FullyKeyedState::KnownFullyKeyed
123 } else {
124 FullyKeyedState::KnownMissingKeys
125 };
126 }
127
128 pub(crate) fn fully_keyed(&self) -> bool {
129 match self.fully_keyed {
130 FullyKeyedState::KnownFullyKeyed => true,
131 FullyKeyedState::KnownMissingKeys => false,
132 FullyKeyedState::Unknown => self.iter().all(|c| c.has_key()),
133 }
134 }
135}
136
137#[cfg(test)]
138mod test {
139 use super::*;
140 use crate::virtual_dom::{VTag, VText};
141
142 #[test]
143 fn mutably_change_children() {
144 let mut vlist = VList::new();
145 assert_eq!(
146 vlist.fully_keyed,
147 FullyKeyedState::KnownFullyKeyed,
148 "should start fully keyed"
149 );
150 vlist.add_child(VNode::VTag({
152 let mut tag = VTag::new("a");
153 tag.key = Some(42u32.into());
154 tag.into()
155 }));
156 assert_eq!(
157 vlist.fully_keyed,
158 FullyKeyedState::KnownFullyKeyed,
159 "should still be fully keyed"
160 );
161 assert_eq!(vlist.len(), 1, "should contain 1 child");
162 vlist.add_child(VNode::VText(VText::new("lorem ipsum")));
164 assert_eq!(
165 vlist.fully_keyed,
166 FullyKeyedState::KnownMissingKeys,
167 "should not be fully keyed, text tags have no key"
168 );
169 let _: &mut [VNode] = &mut vlist; assert_eq!(
171 vlist.fully_keyed,
172 FullyKeyedState::Unknown,
173 "key state should be unknown, since it was potentially modified through children"
174 );
175 }
176}
177
178#[cfg(feature = "ssr")]
179mod feat_ssr {
180 use std::fmt::Write;
181 use std::task::Poll;
182
183 use futures::stream::StreamExt;
184 use futures::{join, pin_mut, poll, FutureExt};
185
186 use super::*;
187 use crate::feat_ssr::VTagKind;
188 use crate::html::AnyScope;
189 use crate::platform::fmt::{self, BufWriter};
190
191 impl VList {
192 pub(crate) async fn render_into_stream(
193 &self,
194 w: &mut BufWriter,
195 parent_scope: &AnyScope,
196 hydratable: bool,
197 parent_vtag_kind: VTagKind,
198 ) {
199 match &self[..] {
200 [] => {}
201 [child] => {
202 child
203 .render_into_stream(w, parent_scope, hydratable, parent_vtag_kind)
204 .await;
205 }
206 _ => {
207 async fn render_child_iter<'a, I>(
208 mut children: I,
209 w: &mut BufWriter,
210 parent_scope: &AnyScope,
211 hydratable: bool,
212 parent_vtag_kind: VTagKind,
213 ) where
214 I: Iterator<Item = &'a VNode>,
215 {
216 let mut w = w;
217 while let Some(m) = children.next() {
218 let child_fur = async move {
219 m.render_into_stream(w, parent_scope, hydratable, parent_vtag_kind)
226 .await;
227 w
228 };
229 pin_mut!(child_fur);
230
231 match poll!(child_fur.as_mut()) {
232 Poll::Pending => {
233 let (mut next_w, next_r) = fmt::buffer();
234 let rest_render_fur = async move {
237 render_child_iter(
238 children,
239 &mut next_w,
240 parent_scope,
241 hydratable,
242 parent_vtag_kind,
243 )
244 .await;
245 }
246 .boxed_local();
248
249 let transfer_fur = async move {
250 let w = child_fur.await;
251
252 pin_mut!(next_r);
253 while let Some(m) = next_r.next().await {
254 let _ = w.write_str(m.as_str());
255 }
256 };
257
258 join!(rest_render_fur, transfer_fur);
259 break;
260 }
261 Poll::Ready(w_) => {
262 w = w_;
263 }
264 }
265 }
266 }
267
268 let children = self.iter();
269 render_child_iter(children, w, parent_scope, hydratable, parent_vtag_kind)
270 .await;
271 }
272 }
273 }
274 }
275}
276
277#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
278#[cfg(feature = "ssr")]
279#[cfg(test)]
280mod ssr_tests {
281 use tokio::test;
282
283 use crate::prelude::*;
284 use crate::LocalServerRenderer as ServerRenderer;
285
286 #[cfg_attr(not(target_os = "wasi"), test)]
287 #[cfg_attr(target_os = "wasi", test(flavor = "current_thread"))]
288 async fn test_text_back_to_back() {
289 #[function_component]
290 fn Comp() -> Html {
291 let s = "world";
292
293 html! { <div>{"Hello "}{s}{"!"}</div> }
294 }
295
296 let s = ServerRenderer::<Comp>::new()
297 .hydratable(false)
298 .render()
299 .await;
300
301 assert_eq!(s, "<div>Hello world!</div>");
302 }
303
304 #[cfg_attr(not(target_os = "wasi"), test)]
305 #[cfg_attr(target_os = "wasi", test(flavor = "current_thread"))]
306 async fn test_fragment() {
307 #[derive(PartialEq, Properties, Debug)]
308 struct ChildProps {
309 name: String,
310 }
311
312 #[function_component]
313 fn Child(props: &ChildProps) -> Html {
314 html! { <div>{"Hello, "}{&props.name}{"!"}</div> }
315 }
316
317 #[function_component]
318 fn Comp() -> Html {
319 html! {
320 <>
321 <Child name="Jane" />
322 <Child name="John" />
323 <Child name="Josh" />
324 </>
325 }
326 }
327
328 let s = ServerRenderer::<Comp>::new()
329 .hydratable(false)
330 .render()
331 .await;
332
333 assert_eq!(
334 s,
335 "<div>Hello, Jane!</div><div>Hello, John!</div><div>Hello, Josh!</div>"
336 );
337 }
338}