1use std::ops::{Deref, DerefMut};
3use std::rc::Rc;
4
5use super::{Key, VNode};
6
7#[doc(hidden)]
8#[derive(Clone, Copy, Debug, PartialEq)]
9pub enum 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 PartialEq for VList {
28 fn eq(&self, other: &Self) -> bool {
29 if self.key != other.key {
30 return false;
31 }
32
33 match (self.children.as_ref(), other.children.as_ref()) {
34 (Some(a), Some(b)) => a == b,
35 (Some(a), None) => a.is_empty(),
36 (None, Some(b)) => b.is_empty(),
37 (None, None) => true,
38 }
39 }
40}
41
42impl Default for VList {
43 fn default() -> Self {
44 Self::new()
45 }
46}
47
48impl Deref for VList {
49 type Target = Vec<VNode>;
50
51 fn deref(&self) -> &Self::Target {
52 match self.children {
53 Some(ref m) => m,
54 None => {
55 const EMPTY: &Vec<VNode> = &Vec::new();
57 EMPTY
58 }
59 }
60 }
61}
62
63impl DerefMut for VList {
64 fn deref_mut(&mut self) -> &mut Self::Target {
65 self.fully_keyed = FullyKeyedState::Unknown;
66 self.children_mut()
67 }
68}
69
70impl<A: Into<VNode>> FromIterator<A> for VList {
71 fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self {
72 let children = iter.into_iter().map(|n| n.into()).collect::<Vec<_>>();
73 if children.is_empty() {
74 VList::new()
75 } else {
76 VList {
77 children: Some(Rc::new(children)),
78 fully_keyed: FullyKeyedState::Unknown,
79 key: None,
80 }
81 }
82 }
83}
84
85impl From<Option<Rc<Vec<VNode>>>> for VList {
86 fn from(children: Option<Rc<Vec<VNode>>>) -> Self {
87 if children.as_ref().map(|x| x.is_empty()).unwrap_or(true) {
88 VList::new()
89 } else {
90 let mut vlist = VList {
91 children,
92 fully_keyed: FullyKeyedState::Unknown,
93 key: None,
94 };
95 vlist.recheck_fully_keyed();
96 vlist
97 }
98 }
99}
100
101impl From<Vec<VNode>> for VList {
102 fn from(children: Vec<VNode>) -> Self {
103 if children.is_empty() {
104 VList::new()
105 } else {
106 let mut vlist = VList {
107 children: Some(Rc::new(children)),
108 fully_keyed: FullyKeyedState::Unknown,
109 key: None,
110 };
111 vlist.recheck_fully_keyed();
112 vlist
113 }
114 }
115}
116
117impl From<VNode> for VList {
118 fn from(child: VNode) -> Self {
119 let mut vlist = VList {
120 children: Some(Rc::new(vec![child])),
121 fully_keyed: FullyKeyedState::Unknown,
122 key: None,
123 };
124 vlist.recheck_fully_keyed();
125 vlist
126 }
127}
128
129impl VList {
130 pub const fn new() -> Self {
132 Self {
133 children: None,
134 key: None,
135 fully_keyed: FullyKeyedState::KnownFullyKeyed,
136 }
137 }
138
139 pub fn with_children(children: Vec<VNode>, key: Option<Key>) -> Self {
141 let mut vlist = VList::from(children);
142 vlist.key = key;
143 vlist
144 }
145
146 #[doc(hidden)]
147 pub fn __macro_new(
149 children: Vec<VNode>,
150 key: Option<Key>,
151 fully_keyed: FullyKeyedState,
152 ) -> Self {
153 VList {
154 children: Some(Rc::new(children)),
155 fully_keyed,
156 key,
157 }
158 }
159
160 fn children_mut(&mut self) -> &mut Vec<VNode> {
164 loop {
165 match self.children {
166 Some(ref mut m) => return Rc::make_mut(m),
167 None => {
168 self.children = Some(Rc::new(Vec::new()));
169 }
170 }
171 }
172 }
173
174 pub fn add_child(&mut self, child: VNode) {
176 if self.fully_keyed == FullyKeyedState::KnownFullyKeyed && !child.has_key() {
177 self.fully_keyed = FullyKeyedState::KnownMissingKeys;
178 }
179 self.children_mut().push(child);
180 }
181
182 pub fn add_children(&mut self, children: impl IntoIterator<Item = VNode>) {
184 let it = children.into_iter();
185 let bound = it.size_hint();
186 self.children_mut().reserve(bound.1.unwrap_or(bound.0));
187 for ch in it {
188 self.add_child(ch);
189 }
190 }
191
192 pub fn recheck_fully_keyed(&mut self) {
197 self.fully_keyed = if self.fully_keyed() {
198 FullyKeyedState::KnownFullyKeyed
199 } else {
200 FullyKeyedState::KnownMissingKeys
201 };
202 }
203
204 pub(crate) fn fully_keyed(&self) -> bool {
205 match self.fully_keyed {
206 FullyKeyedState::KnownFullyKeyed => true,
207 FullyKeyedState::KnownMissingKeys => false,
208 FullyKeyedState::Unknown => self.iter().all(|c| c.has_key()),
209 }
210 }
211}
212
213#[cfg(test)]
214mod test {
215 use super::*;
216 use crate::virtual_dom::{VTag, VText};
217
218 #[test]
219 fn mutably_change_children() {
220 let mut vlist = VList::new();
221 assert_eq!(
222 vlist.fully_keyed,
223 FullyKeyedState::KnownFullyKeyed,
224 "should start fully keyed"
225 );
226 vlist.add_child(VNode::VTag({
228 let mut tag = VTag::new("a");
229 tag.key = Some(42u32.into());
230 tag.into()
231 }));
232 assert_eq!(
233 vlist.fully_keyed,
234 FullyKeyedState::KnownFullyKeyed,
235 "should still be fully keyed"
236 );
237 assert_eq!(vlist.len(), 1, "should contain 1 child");
238 vlist.add_child(VNode::VText(VText::new("lorem ipsum")));
240 assert_eq!(
241 vlist.fully_keyed,
242 FullyKeyedState::KnownMissingKeys,
243 "should not be fully keyed, text tags have no key"
244 );
245 let _: &mut [VNode] = &mut vlist; assert_eq!(
247 vlist.fully_keyed,
248 FullyKeyedState::Unknown,
249 "key state should be unknown, since it was potentially modified through children"
250 );
251 }
252}
253
254#[cfg(feature = "ssr")]
255mod feat_ssr {
256 use std::fmt::Write;
257 use std::task::Poll;
258
259 use futures::stream::StreamExt;
260 use futures::{join, pin_mut, poll, FutureExt};
261
262 use super::*;
263 use crate::feat_ssr::VTagKind;
264 use crate::html::AnyScope;
265 use crate::platform::fmt::{self, BufWriter};
266
267 impl VList {
268 pub(crate) async fn render_into_stream(
269 &self,
270 w: &mut BufWriter,
271 parent_scope: &AnyScope,
272 hydratable: bool,
273 parent_vtag_kind: VTagKind,
274 ) {
275 match &self[..] {
276 [] => {}
277 [child] => {
278 child
279 .render_into_stream(w, parent_scope, hydratable, parent_vtag_kind)
280 .await;
281 }
282 _ => {
283 async fn render_child_iter<'a, I>(
284 mut children: I,
285 w: &mut BufWriter,
286 parent_scope: &AnyScope,
287 hydratable: bool,
288 parent_vtag_kind: VTagKind,
289 ) where
290 I: Iterator<Item = &'a VNode>,
291 {
292 let mut w = w;
293 while let Some(m) = children.next() {
294 let child_fur = async move {
295 m.render_into_stream(w, parent_scope, hydratable, parent_vtag_kind)
302 .await;
303 w
304 };
305 pin_mut!(child_fur);
306
307 match poll!(child_fur.as_mut()) {
308 Poll::Pending => {
309 let (mut next_w, next_r) = fmt::buffer();
310 let rest_render_fur = async move {
313 render_child_iter(
314 children,
315 &mut next_w,
316 parent_scope,
317 hydratable,
318 parent_vtag_kind,
319 )
320 .await;
321 }
322 .boxed_local();
324
325 let transfer_fur = async move {
326 let w = child_fur.await;
327
328 pin_mut!(next_r);
329 while let Some(m) = next_r.next().await {
330 let _ = w.write_str(m.as_str());
331 }
332 };
333
334 join!(rest_render_fur, transfer_fur);
335 break;
336 }
337 Poll::Ready(w_) => {
338 w = w_;
339 }
340 }
341 }
342 }
343
344 let children = self.iter();
345 render_child_iter(children, w, parent_scope, hydratable, parent_vtag_kind)
346 .await;
347 }
348 }
349 }
350 }
351}
352
353#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
354#[cfg(feature = "ssr")]
355#[cfg(test)]
356mod ssr_tests {
357 use tokio::test;
358
359 use crate::prelude::*;
360 use crate::LocalServerRenderer as ServerRenderer;
361
362 #[cfg_attr(not(target_os = "wasi"), test)]
363 #[cfg_attr(target_os = "wasi", test(flavor = "current_thread"))]
364 async fn test_text_back_to_back() {
365 #[component]
366 fn Comp() -> Html {
367 let s = "world";
368
369 html! { <div>{"Hello "}{s}{"!"}</div> }
370 }
371
372 let s = ServerRenderer::<Comp>::new()
373 .hydratable(false)
374 .render()
375 .await;
376
377 assert_eq!(s, "<div>Hello world!</div>");
378 }
379
380 #[cfg_attr(not(target_os = "wasi"), test)]
381 #[cfg_attr(target_os = "wasi", test(flavor = "current_thread"))]
382 async fn test_fragment() {
383 #[derive(PartialEq, Properties, Debug)]
384 struct ChildProps {
385 name: String,
386 }
387
388 #[component]
389 fn Child(props: &ChildProps) -> Html {
390 html! { <div>{"Hello, "}{&props.name}{"!"}</div> }
391 }
392
393 #[component]
394 fn Comp() -> Html {
395 html! {
396 <>
397 <Child name="Jane" />
398 <Child name="John" />
399 <Child name="Josh" />
400 </>
401 }
402 }
403
404 let s = ServerRenderer::<Comp>::new()
405 .hydratable(false)
406 .render()
407 .await;
408
409 assert_eq!(
410 s,
411 "<div>Hello, Jane!</div><div>Hello, John!</div><div>Hello, Josh!</div>"
412 );
413 }
414}