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)); }