xilem/xilem_core/tests/common/mod.rs

296 lines
8.1 KiB
Rust

// Copyright 2024 the Xilem Authors
// SPDX-License-Identifier: Apache-2.0
#![allow(
dead_code,
reason = "This is a utility module, which means that some exposed items aren't used in all instantiations"
)]
#![deny(unreachable_pub)]
#![expect(clippy::allow_attributes_without_reason, reason = "Deferred: Noisy")]
#![expect(clippy::missing_assert_message, reason = "Deferred: Noisy")]
use xilem_core::*;
#[derive(Default)]
pub(super) struct TestCtx(Vec<ViewId>);
impl ViewPathTracker for TestCtx {
fn push_id(&mut self, id: ViewId) {
self.0.push(id);
}
fn pop_id(&mut self) {
self.0
.pop()
.expect("Each pop_id should have a matching push_id");
}
fn view_path(&mut self) -> &[ViewId] {
&self.0
}
}
impl TestCtx {
pub(super) fn assert_empty(&self) {
assert!(
self.0.is_empty(),
"Views should always match push_ids and pop_ids"
);
}
}
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub(super) enum Operation {
Build(u32),
Rebuild { from: u32, to: u32 },
Teardown(u32),
Replace(u32),
}
#[derive(Clone)]
pub(super) struct TestElement {
pub(super) operations: Vec<Operation>,
pub(super) view_path: Vec<ViewId>,
/// The child sequence, if applicable
///
/// This avoids having to create more element types
pub(super) children: Option<SeqChildren>,
}
impl ViewElement for TestElement {
type Mut<'a> = &'a mut Self;
}
/// A view which records all operations which happen on it into the element
///
/// The const generic parameter is used for testing `AnyView`
pub(super) struct OperationView<const N: u32>(pub(super) u32);
#[allow(clippy::manual_non_exhaustive)]
// non_exhaustive is crate level, but this is to "protect" against
// the parent tests from constructing this
pub(super) struct Action {
pub(super) id: u32,
_priv: (),
}
pub(super) struct SequenceView<Seq> {
id: u32,
seq: Seq,
}
pub(super) fn sequence<Seq>(id: u32, seq: Seq) -> SequenceView<Seq>
where
Seq: ViewSequence<(), Action, TestCtx, TestElement>,
{
SequenceView { id, seq }
}
impl<Seq> ViewMarker for SequenceView<Seq> {}
impl<Seq> View<(), Action, TestCtx> for SequenceView<Seq>
where
Seq: ViewSequence<(), Action, TestCtx, TestElement>,
{
type Element = TestElement;
type ViewState = (Seq::SeqState, AppendVec<TestElement>);
fn build(&self, ctx: &mut TestCtx) -> (Self::Element, Self::ViewState) {
let mut elements = AppendVec::default();
let state = self.seq.seq_build(ctx, &mut elements);
(
TestElement {
operations: vec![Operation::Build(self.id)],
children: Some(SeqChildren {
active: elements.into_inner(),
deleted: vec![],
}),
view_path: ctx.view_path().to_vec(),
},
(state, AppendVec::default()),
)
}
fn rebuild(
&self,
prev: &Self,
view_state: &mut Self::ViewState,
ctx: &mut TestCtx,
element: Mut<'_, Self::Element>,
) {
assert_eq!(&*element.view_path, ctx.view_path());
element.operations.push(Operation::Rebuild {
from: prev.id,
to: self.id,
});
let mut elements = SeqTracker {
inner: element.children.as_mut().unwrap(),
ix: 0,
scratch: &mut view_state.1,
};
self.seq
.seq_rebuild(&prev.seq, &mut view_state.0, ctx, &mut elements);
}
fn teardown(
&self,
view_state: &mut Self::ViewState,
ctx: &mut TestCtx,
element: Mut<'_, Self::Element>,
) {
assert_eq!(&*element.view_path, ctx.view_path());
element.operations.push(Operation::Teardown(self.id));
let mut elements = SeqTracker {
inner: element.children.as_mut().unwrap(),
ix: 0,
scratch: &mut view_state.1,
};
self.seq.seq_teardown(&mut view_state.0, ctx, &mut elements);
}
fn message(
&self,
view_state: &mut Self::ViewState,
id_path: &[ViewId],
message: DynMessage,
app_state: &mut (),
) -> MessageResult<Action> {
self.seq
.seq_message(&mut view_state.0, id_path, message, app_state)
}
}
impl<const N: u32> ViewMarker for OperationView<N> {}
impl<const N: u32> View<(), Action, TestCtx> for OperationView<N> {
type Element = TestElement;
type ViewState = ();
fn build(&self, ctx: &mut TestCtx) -> (Self::Element, Self::ViewState) {
(
TestElement {
operations: vec![Operation::Build(self.0)],
view_path: ctx.view_path().to_vec(),
children: None,
},
(),
)
}
fn rebuild(
&self,
prev: &Self,
_: &mut Self::ViewState,
ctx: &mut TestCtx,
element: Mut<'_, Self::Element>,
) {
assert_eq!(&*element.view_path, ctx.view_path());
element.operations.push(Operation::Rebuild {
from: prev.0,
to: self.0,
});
}
fn teardown(
&self,
_: &mut Self::ViewState,
ctx: &mut TestCtx,
element: Mut<'_, Self::Element>,
) {
assert_eq!(&*element.view_path, ctx.view_path());
element.operations.push(Operation::Teardown(self.0));
}
fn message(
&self,
_: &mut Self::ViewState,
_: &[ViewId],
_: DynMessage,
_: &mut (),
) -> MessageResult<Action> {
// If we get an `Action` value, we know it came from here
MessageResult::Action(Action {
_priv: (),
id: self.0,
})
}
}
impl SuperElement<Self, TestCtx> for TestElement {
fn upcast(_ctx: &mut TestCtx, child: Self) -> Self {
child
}
fn with_downcast_val<R>(
this: Self::Mut<'_>,
f: impl FnOnce(Mut<'_, Self>) -> R,
) -> (Self::Mut<'_>, R) {
let ret = f(this);
(this, ret)
}
}
impl AnyElement<Self, TestCtx> for TestElement {
fn replace_inner(this: Self::Mut<'_>, child: Self) -> Self::Mut<'_> {
assert_eq!(child.operations.len(), 1);
let Operation::Build(child_id) = child.operations.first().unwrap() else {
panic!()
};
assert_ne!(child.view_path, this.view_path);
this.operations.push(Operation::Replace(*child_id));
this.view_path = child.view_path;
if let Some((mut new_seq, old_seq)) = child.children.zip(this.children.as_mut()) {
new_seq.deleted.extend(old_seq.deleted.iter().cloned());
new_seq
.deleted
.extend(old_seq.active.iter().cloned().enumerate());
*old_seq = new_seq;
}
this
}
}
#[derive(Clone)]
pub(super) struct SeqChildren {
pub(super) active: Vec<TestElement>,
pub(super) deleted: Vec<(usize, TestElement)>,
}
pub(super) struct SeqTracker<'a> {
scratch: &'a mut AppendVec<TestElement>,
ix: usize,
inner: &'a mut SeqChildren,
}
#[track_caller]
pub(super) fn assert_action(result: MessageResult<Action>, id: u32) {
let MessageResult::Action(inner) = result else {
panic!()
};
assert_eq!(inner.id, id);
}
impl ElementSplice<TestElement> for SeqTracker<'_> {
fn with_scratch<R>(&mut self, f: impl FnOnce(&mut AppendVec<TestElement>) -> R) -> R {
let ret = f(self.scratch);
for element in self.scratch.drain() {
self.inner.active.push(element);
}
ret
}
fn insert(&mut self, element: TestElement) {
self.inner.active.push(element);
}
fn mutate<R>(&mut self, f: impl FnOnce(Mut<'_, TestElement>) -> R) -> R {
let ix = self.ix;
self.ix += 1;
f(&mut self.inner.active[ix])
}
fn skip(&mut self, n: usize) {
self.ix += n;
}
fn delete<R>(&mut self, f: impl FnOnce(Mut<'_, TestElement>) -> R) -> R {
let ret = f(&mut self.inner.active[self.ix]);
let val = self.inner.active.remove(self.ix);
self.inner.deleted.push((self.ix, val));
ret
}
}