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