|
70 | 70 | _get_source_index, |
71 | 71 | _xblock_type_and_display_name, |
72 | 72 | add_container_page_publishing_info, |
73 | | - create_xblock_info, |
| 73 | + create_xblock_info, duplicate_block, update_from_source, |
74 | 74 | ) |
75 | 75 |
|
76 | 76 |
|
@@ -789,6 +789,29 @@ def verify_name(source_usage_key, parent_usage_key, expected_name, display_name= |
789 | 789 | # Now send a custom display name for the duplicate. |
790 | 790 | verify_name(self.seq_usage_key, self.chapter_usage_key, "customized name", display_name="customized name") |
791 | 791 |
|
| 792 | + def test_shallow_duplicate(self): |
| 793 | + """ |
| 794 | + Test that shallow_duplicate creates a new block. |
| 795 | + """ |
| 796 | + source_course = CourseFactory() |
| 797 | + user = UserFactory.create() |
| 798 | + source_chapter = BlockFactory(parent=source_course, category='chapter', display_name='Source Chapter') |
| 799 | + BlockFactory(parent=source_chapter, category='html', display_name='Child') |
| 800 | + # Refresh. |
| 801 | + source_chapter = self.store.get_item(source_chapter.location) |
| 802 | + self.assertEqual(len(source_chapter.get_children()), 1) |
| 803 | + destination_course = CourseFactory() |
| 804 | + destination_location = duplicate_block( |
| 805 | + parent_usage_key=destination_course.location, duplicate_source_usage_key=source_chapter.location, |
| 806 | + user=user, |
| 807 | + display_name=source_chapter.display_name, |
| 808 | + shallow=True, |
| 809 | + ) |
| 810 | + # Refresh here, too, just to be sure. |
| 811 | + destination_chapter = self.store.get_item(destination_location) |
| 812 | + self.assertEqual(len(destination_chapter.get_children()), 0) |
| 813 | + self.assertEqual(destination_chapter.display_name, 'Source Chapter') |
| 814 | + |
792 | 815 |
|
793 | 816 | @ddt.ddt |
794 | 817 | class TestMoveItem(ItemTest): |
@@ -3620,3 +3643,111 @@ def test_creator_show_delete_button_with_waffle(self): |
3620 | 3643 | ) |
3621 | 3644 |
|
3622 | 3645 | self.assertFalse(xblock_info['show_delete_button']) |
| 3646 | + |
| 3647 | + |
| 3648 | +@patch('xmodule.modulestore.split_mongo.caching_descriptor_system.CachingDescriptorSystem.applicable_aside_types', |
| 3649 | + lambda self, block: ['test_aside']) |
| 3650 | +class TestUpdateFromSource(ModuleStoreTestCase): |
| 3651 | + """ |
| 3652 | + Test update_from_source. |
| 3653 | + """ |
| 3654 | + |
| 3655 | + def setUp(self): |
| 3656 | + """ |
| 3657 | + Set up the runtime for tests. |
| 3658 | + """ |
| 3659 | + super().setUp() |
| 3660 | + key_store = DictKeyValueStore() |
| 3661 | + field_data = KvsFieldData(key_store) |
| 3662 | + self.runtime = TestRuntime(services={'field-data': field_data}) |
| 3663 | + |
| 3664 | + def create_source_block(self, course): |
| 3665 | + """ |
| 3666 | + Create a chapter with all the fixings. |
| 3667 | + """ |
| 3668 | + source_block = BlockFactory( |
| 3669 | + parent=course, |
| 3670 | + category='course_info', |
| 3671 | + display_name='Source Block', |
| 3672 | + metadata={'due': datetime(2010, 11, 22, 4, 0, tzinfo=UTC)}, |
| 3673 | + ) |
| 3674 | + |
| 3675 | + def_id = self.runtime.id_generator.create_definition('html') |
| 3676 | + usage_id = self.runtime.id_generator.create_usage(def_id) |
| 3677 | + |
| 3678 | + aside = AsideTest(scope_ids=ScopeIds('user', 'html', def_id, usage_id), runtime=self.runtime) |
| 3679 | + aside.field11 = 'html_new_value1' |
| 3680 | + |
| 3681 | + # The data attribute is handled in a special manner and should be updated. |
| 3682 | + source_block.data = '<div>test</div>' |
| 3683 | + # This field is set on the content scope (definition_data), which should be updated. |
| 3684 | + source_block.items = ['test', 'beep'] |
| 3685 | + |
| 3686 | + self.store.update_item(source_block, self.user.id, asides=[aside]) |
| 3687 | + |
| 3688 | + # quick sanity checks |
| 3689 | + source_block = self.store.get_item(source_block.location) |
| 3690 | + self.assertEqual(source_block.due, datetime(2010, 11, 22, 4, 0, tzinfo=UTC)) |
| 3691 | + self.assertEqual(source_block.display_name, 'Source Block') |
| 3692 | + self.assertEqual(source_block.runtime.get_asides(source_block)[0].field11, 'html_new_value1') |
| 3693 | + self.assertEqual(source_block.data, '<div>test</div>') |
| 3694 | + self.assertEqual(source_block.items, ['test', 'beep']) |
| 3695 | + |
| 3696 | + return source_block |
| 3697 | + |
| 3698 | + def check_updated(self, source_block, destination_key): |
| 3699 | + """ |
| 3700 | + Check that the destination block has been updated to match our source block. |
| 3701 | + """ |
| 3702 | + revised = self.store.get_item(destination_key) |
| 3703 | + self.assertEqual(source_block.display_name, revised.display_name) |
| 3704 | + self.assertEqual(source_block.due, revised.due) |
| 3705 | + self.assertEqual(revised.data, source_block.data) |
| 3706 | + self.assertEqual(revised.items, source_block.items) |
| 3707 | + |
| 3708 | + self.assertEqual( |
| 3709 | + revised.runtime.get_asides(revised)[0].field11, |
| 3710 | + source_block.runtime.get_asides(source_block)[0].field11, |
| 3711 | + ) |
| 3712 | + |
| 3713 | + @XBlockAside.register_temp_plugin(AsideTest, 'test_aside') |
| 3714 | + def test_update_from_source(self): |
| 3715 | + """ |
| 3716 | + Test that update_from_source updates the destination block. |
| 3717 | + """ |
| 3718 | + course = CourseFactory() |
| 3719 | + user = UserFactory.create() |
| 3720 | + |
| 3721 | + source_block = self.create_source_block(course) |
| 3722 | + |
| 3723 | + destination_block = BlockFactory(parent=course, category='course_info', display_name='Destination Problem') |
| 3724 | + update_from_source(source_block=source_block, destination_block=destination_block, user_id=user.id) |
| 3725 | + self.check_updated(source_block, destination_block.location) |
| 3726 | + |
| 3727 | + @XBlockAside.register_temp_plugin(AsideTest, 'test_aside') |
| 3728 | + def test_update_clobbers(self): |
| 3729 | + """ |
| 3730 | + Verify that our update clobbers everything. |
| 3731 | + """ |
| 3732 | + course = CourseFactory() |
| 3733 | + user = UserFactory.create() |
| 3734 | + |
| 3735 | + source_block = self.create_source_block(course) |
| 3736 | + |
| 3737 | + destination_block = BlockFactory( |
| 3738 | + parent=course, |
| 3739 | + category='course_info', |
| 3740 | + display_name='Destination Chapter', |
| 3741 | + metadata={'due': datetime(2025, 10, 21, 6, 5, tzinfo=UTC)}, |
| 3742 | + ) |
| 3743 | + |
| 3744 | + def_id = self.runtime.id_generator.create_definition('html') |
| 3745 | + usage_id = self.runtime.id_generator.create_usage(def_id) |
| 3746 | + aside = AsideTest(scope_ids=ScopeIds('user', 'html', def_id, usage_id), runtime=self.runtime) |
| 3747 | + aside.field11 = 'Other stuff' |
| 3748 | + destination_block.data = '<div>other stuff</div>' |
| 3749 | + destination_block.items = ['other stuff', 'boop'] |
| 3750 | + self.store.update_item(destination_block, user.id, asides=[aside]) |
| 3751 | + |
| 3752 | + update_from_source(source_block=source_block, destination_block=destination_block, user_id=user.id) |
| 3753 | + self.check_updated(source_block, destination_block.location) |
0 commit comments