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