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!(
206 result
207 .unwrap_err()
208 .to_string()
209 .contains("recursion limit exceeded")
210 );
211 }
212
213 #[test]
216 fn test_hashed_item_stack_roundtrip() {
217 let mut hashed = HashedItemStack::EMPTY;
218 hashed.item = ItemKind::IronIngot;
219 hashed.count = 10;
220 hashed.components[1] = Patchable::Added(((), 123456));
222
223 roundtrip(&hashed);
224 }
225
226 #[test]
227 fn test_hashed_item_stack_empty() {
228 let hashed = HashedItemStack::EMPTY;
229 let mut buf = Vec::new();
230 hashed.encode(&mut buf).unwrap();
231
232 let mut slice = buf.as_slice();
233 let decoded = HashedItemStack::decode(&mut slice).unwrap();
234 assert!(decoded.is_empty());
235 }
236
237 #[test]
240 fn test_invalid_component_id() {
241 let mut buf = Vec::new();
242 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();
248 let result = ItemStack::decode(&mut slice);
249 assert!(result.is_err());
250 }
251
252 #[test]
253 fn test_all_item_component_ids() {
254 let mut ids = std::collections::HashSet::new();
255 let components = vec![
256 ItemComponent::CustomData(Compound::default()),
257 ItemComponent::MaxStackSize(VarInt(64)),
258 ItemComponent::Unbreakable,
259 ItemComponent::Glider,
260 ItemComponent::ShulkerColor(DyeColor::Black),
261 ];
262
263 for comp in components {
264 let id = comp.id();
265 assert!(id < NUM_ITEM_COMPONENTS as u32);
266 ids.insert(id);
267 }
268
269 assert!(ids.contains(&4));
270 assert!(ids.contains(&30));
271 }
272
273 #[test]
274 fn test_food_component_serialization() {
275 let food = ItemComponent::Food {
276 nutrition: VarInt(4),
277 saturation_modifier: 0.5,
278 can_always_eat: true,
279 };
280 let mut stack = ItemStack::new(ItemKind::Apple, 1);
281 stack.insert_component(food);
282 roundtrip(&stack);
283 }
284
285 #[test]
286 fn test_attribute_modifiers_serialization() {
287 let modifier = AttributeModifier {
288 attribute_id: RegistryId::new(0),
289 modifier_id: ident!("test_mod").into(),
290 value: 5.0,
291 operation: EntityAttributeOperation::Add,
292 slot: AttributeSlot::MainHand,
293 };
294
295 let comp = ItemComponent::AttributeModifiers {
296 modifiers: vec![modifier],
297 };
298
299 let mut stack = ItemStack::new(ItemKind::NetheriteSword, 1);
300 stack.insert_component(comp);
301 roundtrip(&stack);
302 }
303}