public void TestTopParentSerialization(string appName, string topParentName)
        {
            var root = Path.Combine(Environment.CurrentDirectory, "Apps", appName);

            Assert.IsTrue(File.Exists(root));

            (var msapp, var errors) = CanvasDocument.LoadFromMsapp(root);
            errors.ThrowOnErrors();

            using (var tempDir = new TempDir())
            {
                string outSrcDir = tempDir.Dir;

                // Save to sources
                msapp.SaveToSources(outSrcDir);

                // Go find the source file for the editor state
                string filename     = $"{topParentName}{EditorStateFileExtension}";
                string fullFilePath = Path.Combine(outSrcDir, "Src", "EditorState", filename);
                if (File.Exists(fullFilePath))
                {
                    // Get the file for the specific control we're looking for
                    DirectoryReader.Entry file        = new DirectoryReader.Entry(fullFilePath);
                    ControlTreeState      editorState = file.ToObject <ControlTreeState>();

                    // Check that the IsTopParent was set correctly
                    Assert.AreEqual(topParentName, editorState.TopParentName);
                }
                else
                {
                    Assert.Fail($"Could not find expected file {fullFilePath}.");
                }
            }
        }
        /// This writes out the IR, editor state cache, and potentially component templates
        /// for a single top level control, such as the App object, a screen, or component
        /// Name refers to the control name
        /// Only in case of AppTest, the topParentName is passed down, since for AppTest the TestSuites are sharded into individual files.
        /// We truncate the control names to limit it to 50 charactes length (escaped name).
        private static void WriteTopParent(
            DirectoryWriter dir,
            CanvasDocument app,
            string name,
            BlockNode ir,
            string subDir,
            string topParentName = null)
        {
            var controlName    = name;
            var newControlName = Utilities.TruncateNameIfTooLong(controlName);

            string filename = newControlName + ".fx.yaml";

            // For AppTest control shard each test suite into individual files.
            if (controlName == AppTestControlName)
            {
                foreach (var child in ir.Children)
                {
                    WriteTopParent(dir, app, child.Properties.FirstOrDefault(x => x.Identifier == "DisplayName").Expression.Expression.Trim(new char[] { '"' }), child, subDir, controlName);
                }

                // Clear the children since they have already been sharded into their individual files.
                ir.Children.Clear();
            }

            var text = PAWriterVisitor.PrettyPrint(ir);

            dir.WriteAllText(subDir, filename, text);

            // For TestSuite controls, only the top parent control has an editor state created.
            // For other control types, create an editor state.
            if (string.IsNullOrEmpty(topParentName))
            {
                string editorStateFilename = $"{newControlName}.editorstate.json";

                var controlStates = new Dictionary <string, ControlState>();
                foreach (var item in app._editorStateStore.GetControlsWithTopParent(controlName))
                {
                    controlStates.Add(item.Name, item);
                }

                ControlTreeState editorState = new ControlTreeState
                {
                    ControlStates = controlStates,
                    TopParentName = controlName
                };

                // Write out of all the other state properties on the control for roundtripping.
                dir.WriteAllJson(EditorStateDir, editorStateFilename, editorState);
            }

            // Write out component templates next to the component
            if (app._templateStore.TryGetTemplate(name, out var templateState))
            {
                dir.WriteAllJson(subDir, newControlName + ".json", templateState);
            }
        }
        public void TestTopParentNameFallback(string appName, string topParentName)
        {
            var root = Path.Combine(Environment.CurrentDirectory, "Apps", appName);

            Assert.IsTrue(File.Exists(root));

            (var msapp, var errors) = CanvasDocument.LoadFromMsapp(root);
            errors.ThrowOnErrors();

            using (var tempDir = new TempDir())
            {
                string outSrcDir = tempDir.Dir;

                // Save to sources
                msapp.SaveToSources(outSrcDir);

                // Go find the source file for the editor state
                string filename     = $"{topParentName}.editorstate.json";
                string fullFilePath = Path.Combine(outSrcDir, "Src", "EditorState", filename);
                if (File.Exists(fullFilePath))
                {
                    // Get the file for the specific control we're looking for
                    DirectoryReader.Entry file        = new DirectoryReader.Entry(fullFilePath);
                    ControlTreeState      editorState = file.ToObject <ControlTreeState>();

                    // Rename the file so we know that the file name itself is used.
                    string newFileName = Guid.NewGuid().ToString();
                    string newFilePath = Path.Combine(outSrcDir, "Src", "EditorState");

                    // Write out only the dictionary to the file, which is the older format.
                    DirectoryWriter dir = new DirectoryWriter(newFilePath);
                    dir.WriteAllJson(newFilePath, $"{newFileName}{EditorStateFileExtension}", editorState.ControlStates);

                    // Remove the old file, we only want the re-written and re-named file
                    File.Delete(fullFilePath);

                    // Load app from the source folder
                    var app = SourceSerializer.LoadFromSource(outSrcDir, new ErrorContainer());

                    // Find the relevant controls and check their top parent name
                    foreach (var control in editorState.ControlStates)
                    {
                        app._editorStateStore.TryGetControlState(control.Value.Name, out ControlState state);
                        Assert.AreEqual(newFileName, state.TopParentName);
                    }
                }
                else
                {
                    Assert.Fail($"Could not find expected file {fullFilePath}.");
                }
            }
        }
 // For backwards compat purposes. We may not have the new model for the
 // editor state file if the app was unpacked prior to these changes.
 // In this case, revert back to the using previous functionality.
 //
 // When SourceSerializer is updated past v24, this could be removed entirely.
 private static void ApplyV24BackCompat(ControlTreeState editorState, DirectoryReader.Entry file)
 {
     editorState.ControlStates = file.ToObject <Dictionary <string, ControlState> >();
     editorState.TopParentName = Utilities.UnEscapeFilename(file._relativeName.Replace(".editorstate.json", ""));
 }
        private static void LoadSourceFiles(CanvasDocument app, DirectoryReader directory, Dictionary <string, ControlTemplate> templateDefaults, ErrorContainer errors)
        {
            foreach (var file in directory.EnumerateFiles(EditorStateDir, "*.json"))
            {
                if (!file._relativeName.EndsWith(".editorstate.json"))
                {
                    errors.FormatNotSupported($"Unexpected file present in {EditorStateDir}");
                    throw new DocumentException();
                }

                // Json peer to a .pa file.
                ControlTreeState editorState = file.ToObject <ControlTreeState>();
                if (editorState.ControlStates == null)
                {
                    ApplyV24BackCompat(editorState, file);
                }

                foreach (var control in editorState.ControlStates)
                {
                    control.Value.TopParentName = editorState.TopParentName;
                    if (!app._editorStateStore.TryAddControl(control.Value))
                    {
                        // Can't have duplicate control names.
                        // This might happen due to a bad merge.
                        errors.EditorStateError(file.SourceSpan, $"Control '{control.Value.Name}' is already defined.");
                    }
                }
            }

            // For now, the Themes file lives in CodeDir as a json file
            // We'd like to make this .fx.yaml as well eventually
            foreach (var file in directory.EnumerateFiles(CodeDir, "*.json", searchSubdirectories: false))
            {
                if (Path.GetFileName(file._relativeName) == "Themes.json")
                {
                    app._themes = file.ToObject <ThemesJson>();
                }
            }

            foreach (var file in EnumerateComponentDirs(directory, "*.fx.yaml"))
            {
                AddControl(app, file._relativeName, true, file.GetContents(), errors);
            }

            foreach (var file in EnumerateComponentDirs(directory, "*.json"))
            {
                var componentTemplate = file.ToObject <CombinedTemplateState>();
                app._templateStore.AddTemplate(componentTemplate.ComponentManifest.Name, componentTemplate);
            }

            foreach (var file in directory.EnumerateFiles(CodeDir, "*.fx.yaml", searchSubdirectories: false))
            {
                AddControl(app, file._relativeName, false, file.GetContents(), errors);
            }

            // When loading TestSuites sharded files, add them within the top parent AppTest control (i.e. Test_7F478737223C4B69)
            // Make sure to load the the Test_7F478737223C4B69.fx.yaml file first to add the top parent control.
            var shardedTestSuites = new List <DirectoryReader.Entry>();

            foreach (var file in directory.EnumerateFiles(TestDir, "*.fx.yaml"))
            {
                if (file.Kind == FileKind.AppTestParentControl)
                {
                    AddControl(app, file._relativeName, false, file.GetContents(), errors);
                }
                else
                {
                    shardedTestSuites.Add(file);
                }
            }
            shardedTestSuites.ForEach(x => AddControl(app, x._relativeName, false, x.GetContents(), errors));
        }