1mod components;
2mod impls;
3mod stack;
4mod vanilla_components;
5
6pub(crate) const NUM_ITEM_COMPONENTS: usize = 96;
7pub(crate) const MAX_RECURSION_DEPTH: usize = 16;
10
11pub use chunkedge_generated::item::ItemKind;
12
13pub use crate::components::{
14 AttributeModifier, AttributeSlot, AxolotlType, BannerLayer, BannerPattern, BeeData,
15 BlockPredicate, ConsumableAnimation, ConsumeEffect, ConsumeEffectData, DamageReduction,
16 DyeColor, EquipSlot, ExactComponentMatcher, FireworkExplosionData, FoxType, HorseColor,
17 InstrumentDefinition, ItemComponent, JukeboxSong, LlamaColor, LodestoneTarget,
18 MapPostProcessingType, ModePair, MooshroomType, PaintingVariantDefinition, ParrotType,
19 PartialComponentMatcher, PotionEffect, ProfileProperty, Property, PropertyValue, RabbitType,
20 Rarity, ResolvableProfile, SalmonScale, SoundEventDefinition, ToolRule, TrimMaterial,
21 TrimPattern, TropicalFishPattern, WritablePage, WrittenPage,
22};
23pub use crate::impls::decode_item_stack_recursive;
24pub use crate::stack::{HashedItemStack, ItemStack};
25
26#[cfg(test)]
27mod tests {
28 use chunkedge_binary::{Decode, Encode, VarInt};
29 use chunkedge_generated::attributes::EntityAttributeOperation;
30 use chunkedge_generated::item::ItemKind;
31 use chunkedge_generated::registry_id::RegistryId;
32 use chunkedge_ident::ident;
33 use chunkedge_nbt::Compound;
34 use chunkedge_text::Text;
35
36 use super::*;
37 use crate::components::{
38 AttributeModifier, AttributeSlot, DyeColor, ModePair, Patchable, PropertyValue, Rarity,
39 };
40
41 fn create_test_stack(item: ItemKind, count: i8) -> ItemStack {
44 ItemStack::new(item, count)
45 }
46
47 fn roundtrip<T: Encode + for<'a> Decode<'a> + PartialEq + std::fmt::Debug>(val: &T) {
48 let mut buf = Vec::new();
49 val.encode(&mut buf).expect("Failed to encode");
50 let mut slice = buf.as_slice();
51 let decoded = T::decode(&mut slice).expect("Failed to decode");
52 assert_eq!(val, &decoded, "Roundtrip failed equality check");
53 assert!(slice.is_empty(), "Buffer not fully consumed");
54 }
55
56 #[test]
59 fn test_patchable_logic() {
60 let p_added = Patchable::Added((Box::new(10), 123));
61 let p_default = Patchable::Default(Box::new(5));
62 let p_removed: Patchable<Box<i32>> = Patchable::Removed;
63 let p_none: Patchable<Box<i32>> = Patchable::None;
64
65 assert_eq!(p_added.as_option().map(|v| **v), Some(10));
66 assert_eq!(p_default.as_option().map(|v| **v), Some(5));
67 assert_eq!(p_removed.as_option(), None);
68 assert_eq!(p_none.as_option(), None);
69 }
70
71 #[test]
74 fn test_item_stack_empty() {
75 let empty = ItemStack::EMPTY;
76 assert!(empty.is_empty());
77
78 let air = ItemStack::new(ItemKind::Air, 1);
79 assert!(air.is_empty());
80
81 let stack = ItemStack::new(ItemKind::Diamond, 0);
82 assert!(stack.is_empty());
83
84 let stack = ItemStack::new(ItemKind::Diamond, -1);
85 assert!(stack.is_empty());
86 }
87
88 #[test]
89 fn test_component_insertion_and_removal() {
90 let mut stack = create_test_stack(ItemKind::Diamond, 1);
91
92 let custom_name = ItemComponent::CustomName(Text::from("Test Item").into());
94 stack.insert_component(custom_name.clone());
95
96 assert_eq!(stack.get_component(5_usize), Some(&custom_name));
97 assert_eq!(stack.components().len(), 1);
98
99 let removed = stack.remove_component(5_usize);
101 assert_eq!(removed, Some(custom_name));
102 assert_eq!(stack.get_component(5_usize), None);
103
104 assert!(matches!(stack.components[5], Patchable::Removed));
106 }
107
108 #[test]
111 fn test_serialization_basic_stack() {
112 let stack = ItemStack::new(ItemKind::Stone, 32);
113 roundtrip(&stack);
114 }
115
116 #[test]
117 fn test_serialization_with_complex_components() {
118 let mut stack = ItemStack::new(ItemKind::DiamondSword, 1);
119
120 stack.insert_component(ItemComponent::Damage(VarInt(50)));
122 stack.insert_component(ItemComponent::Rarity(Rarity::Epic));
123
124 stack.insert_component(ItemComponent::Enchantments(vec![(
126 RegistryId::new(1),
127 VarInt(5),
128 )]));
129
130 roundtrip(&stack);
131 }
132
133 #[test]
134 fn test_serialization_removed_components() {
135 let mut stack = ItemStack::new(ItemKind::Diamond, 1);
137 stack.insert_component(ItemComponent::Unbreakable);
138 stack.remove_component(ItemComponent::Unbreakable.id() as usize);
139
140 roundtrip(&stack);
141 }
142
143 #[test]
144 fn test_mode_pair_serialization() {
145 let m0 = ModePair::<String, RegistryId>::Mode0("minecraft:standard".to_owned());
146 let m1 = ModePair::<String, RegistryId>::Mode1(RegistryId::new(1));
147
148 roundtrip(&m0);
149 roundtrip(&m1);
150 }
151
152 #[test]
153 fn test_property_value_serialization() {
154 let exact = PropertyValue::Exact("true".into());
155 let min_max = PropertyValue::MinMax {
156 min: "1".into(),
157 max: "5".into(),
158 };
159
160 roundtrip(&exact);
161 roundtrip(&min_max);
162 }
163
164 #[test]
167 fn test_nested_container_serialization() {
168 let mut inner_stack = ItemStack::new(ItemKind::Apple, 1);
169 inner_stack.insert_component(ItemComponent::ItemName(Text::from("Inner").into()));
170
171 let mut outer_stack = ItemStack::new(ItemKind::Chest, 1);
172 outer_stack.insert_component(ItemComponent::Container(vec![inner_stack]));
173
174 roundtrip(&outer_stack);
175 }
176
177 #[test]
178 fn test_recursion_limit() {
179 let mut buf = Vec::new();
180
181 fn write_recursive_bundle(w: &mut Vec<u8>, depth: usize) {
183 VarInt(1).encode(&mut *w).unwrap(); ItemKind::Bundle.encode(&mut *w).unwrap(); VarInt(1).encode(&mut *w).unwrap(); VarInt(0).encode(&mut *w).unwrap(); VarInt(41).encode(&mut *w).unwrap(); if depth > 0 {
192 VarInt(1).encode(&mut *w).unwrap(); write_recursive_bundle(w, depth - 1);
194 } else {
195 VarInt(0).encode(&mut *w).unwrap(); }
197 }
198
199 write_recursive_bundle(&mut buf, 20); let mut slice = buf.as_slice();
202 let result = ItemStack::decode(&mut slice);
203
204 assert!(result.is_err());
205 assert!(result
206 .unwrap_err()
207 .to_string()
208 .contains("recursion limit exceeded"));
209 }
210
211 #[test]
214 fn test_hashed_item_stack_roundtrip() {
215 let mut hashed = HashedItemStack::EMPTY;
216 hashed.item = ItemKind::IronIngot;
217 hashed.count = 10;
218 hashed.components[1] = Patchable::Added(((), 123456));
220
221 roundtrip(&hashed);
222 }
223
224 #[test]
225 fn test_hashed_item_stack_empty() {
226 let hashed = HashedItemStack::EMPTY;
227 let mut buf = Vec::new();
228 hashed.encode(&mut buf).unwrap();
229
230 let mut slice = buf.as_slice();
231 let decoded = HashedItemStack::decode(&mut slice).unwrap();
232 assert!(decoded.is_empty());
233 }
234
235 #[test]
238 fn test_invalid_component_id() {
239 let mut buf = Vec::new();
240 VarInt(1).encode(&mut buf).unwrap(); ItemKind::Stone.encode(&mut buf).unwrap(); VarInt(1).encode(&mut buf).unwrap(); VarInt(999).encode(&mut buf).unwrap(); let mut slice = buf.as_slice();
246 let result = ItemStack::decode(&mut slice);
247 assert!(result.is_err());
248 }
249
250 #[test]
251 fn test_all_item_component_ids() {
252 let mut ids = std::collections::HashSet::new();
253 let components = vec![
254 ItemComponent::CustomData(Compound::default()),
255 ItemComponent::MaxStackSize(VarInt(64)),
256 ItemComponent::Unbreakable,
257 ItemComponent::Glider,
258 ItemComponent::ShulkerColor(DyeColor::Black),
259 ];
260
261 for comp in components {
262 let id = comp.id();
263 assert!(id < NUM_ITEM_COMPONENTS as u32);
264 ids.insert(id);
265 }
266
267 assert!(ids.contains(&4));
268 assert!(ids.contains(&30));
269 }
270
271 #[test]
272 fn test_food_component_serialization() {
273 let food = ItemComponent::Food {
274 nutrition: VarInt(4),
275 saturation_modifier: 0.5,
276 can_always_eat: true,
277 };
278 let mut stack = ItemStack::new(ItemKind::Apple, 1);
279 stack.insert_component(food);
280 roundtrip(&stack);
281 }
282
283 #[test]
284 fn test_attribute_modifiers_serialization() {
285 let modifier = AttributeModifier {
286 attribute_id: RegistryId::new(0),
287 modifier_id: ident!("test_mod").into(),
288 value: 5.0,
289 operation: EntityAttributeOperation::Add,
290 slot: AttributeSlot::MainHand,
291 };
292
293 let comp = ItemComponent::AttributeModifiers {
294 modifiers: vec![modifier],
295 };
296
297 let mut stack = ItemStack::new(ItemKind::NetheriteSword, 1);
298 stack.insert_component(comp);
299 roundtrip(&stack);
300 }
301}