diff --git a/Backend.Tests/Controllers/LiftControllerTests.cs b/Backend.Tests/Controllers/LiftControllerTests.cs index 2b50668249..2d9431419b 100644 --- a/Backend.Tests/Controllers/LiftControllerTests.cs +++ b/Backend.Tests/Controllers/LiftControllerTests.cs @@ -1,13 +1,11 @@ using System; using System.Collections.Generic; using System.IO; -using System.IO.Compression; -using System.Linq; using System.Text; using System.Text.RegularExpressions; using Backend.Tests.Mocks; using BackendFramework.Controllers; -using BackendFramework.Helper; +using static BackendFramework.Helper.FileUtilities; using BackendFramework.Interfaces; using BackendFramework.Models; using BackendFramework.Services; @@ -168,19 +166,7 @@ private static string ExtractZipFileContents(byte[] fileContents) { var zipFile = Path.GetTempFileName(); File.WriteAllBytes(zipFile, fileContents); - var extractionPath = ExtractZipFile(zipFile, true); - return extractionPath; - } - - private static string ExtractZipFile(string zipFilePath, bool deleteZipFile = false) - { - var extractionPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - Directory.CreateDirectory(extractionPath); - ZipFile.ExtractToDirectory(zipFilePath, extractionPath); - if (deleteZipFile) - { - File.Delete(zipFilePath); - } + var extractionPath = ExtractZipFile(zipFile, null, true); return extractionPath; } @@ -284,110 +270,113 @@ public void TestRoundtrip() _projServ.Create(proj); // Generate api parameter with filestream - if (File.Exists(pathToStartZip)) + if (!File.Exists(pathToStartZip)) + { + continue; + } + + using (var fstream = File.OpenRead(pathToStartZip)) { - var fstream = File.OpenRead(pathToStartZip); var fileUpload = InitFile(fstream, filename); // Make api call var result = _liftController.UploadLiftFile(proj.Id, fileUpload).Result; Assert.That(!(result is BadRequestObjectResult)); + } - proj = _projServ.GetProject(proj.Id).Result; - Assert.AreEqual(proj.VernacularWritingSystem.Bcp47, roundTripContents.Language); - - fstream.Close(); + proj = _projServ.GetProject(proj.Id).Result; + Assert.AreEqual(proj.VernacularWritingSystem.Bcp47, roundTripContents.Language); + Assert.That(proj.LiftImported); - var allWords = _wordrepo.GetAllWords(proj.Id).Result; - Assert.AreEqual(allWords.Count, roundTripContents.NumOfWords); - // We are currently only testing guids on the single-entry data sets - if (roundTripContents.EntryGuid != "" && allWords.Count == 1) + var allWords = _wordrepo.GetAllWords(proj.Id).Result; + Assert.AreEqual(allWords.Count, roundTripContents.NumOfWords); + // We are currently only testing guids on the single-entry data sets + if (roundTripContents.EntryGuid != "" && allWords.Count == 1) + { + Assert.AreEqual(allWords[0].Guid.ToString(), roundTripContents.EntryGuid); + if (roundTripContents.SenseGuid != "") { - Assert.AreEqual(allWords[0].Guid.ToString(), roundTripContents.EntryGuid); - if (roundTripContents.SenseGuid != "") - { - Assert.AreEqual(allWords[0].Senses[0].Guid.ToString(), roundTripContents.SenseGuid); - } + Assert.AreEqual(allWords[0].Senses[0].Guid.ToString(), roundTripContents.SenseGuid); } + } - // Export - var exportedFilePath = _liftController.CreateLiftExport(proj.Id); - var exportedDirectory = ExtractZipFile(exportedFilePath, false); + // Export + var exportedFilePath = _liftController.CreateLiftExport(proj.Id); + var exportedDirectory = ExtractZipFile(exportedFilePath, null, false); - // Assert the file was created with desired heirarchy - Assert.That(Directory.Exists(exportedDirectory)); - Assert.That(Directory.Exists(Path.Combine(exportedDirectory, "Lift", "audio"))); - foreach (var audioFile in roundTripContents.AudioFiles) - { - Assert.That(File.Exists(Path.Combine( - exportedDirectory, "Lift", "audio", audioFile))); - } - Assert.That(Directory.Exists(Path.Combine(exportedDirectory, "Lift", "WritingSystems"))); + // Assert the file was created with desired heirarchy + Assert.That(Directory.Exists(exportedDirectory)); + Assert.That(Directory.Exists(Path.Combine(exportedDirectory, "Lift", "audio"))); + foreach (var audioFile in roundTripContents.AudioFiles) + { Assert.That(File.Exists(Path.Combine( - exportedDirectory, - "Lift", "WritingSystems", roundTripContents.Language + ".ldml"))); - Assert.That(File.Exists(Path.Combine(exportedDirectory, "Lift", "NewLiftFile.lift"))); - Directory.Delete(exportedDirectory, true); + exportedDirectory, "Lift", "audio", audioFile))); + } + Assert.That(Directory.Exists(Path.Combine(exportedDirectory, "Lift", "WritingSystems"))); + Assert.That(File.Exists(Path.Combine( + exportedDirectory, + "Lift", "WritingSystems", roundTripContents.Language + ".ldml"))); + Assert.That(File.Exists(Path.Combine(exportedDirectory, "Lift", "NewLiftFile.lift"))); + Directory.Delete(exportedDirectory, true); - _wordrepo.DeleteAllWords(proj.Id); + _wordrepo.DeleteAllWords(proj.Id); - // Roundtrip Part 2 + // Roundtrip Part 2 - // Upload the exported words again - // Init the project the .zip info is added to - var proj2 = RandomProject(); - _projServ.Create(proj2); + // Upload the exported words again + // Init the project the .zip info is added to + var proj2 = RandomProject(); + _projServ.Create(proj2); - // Generate api parameter with filestream - fstream = File.OpenRead(exportedFilePath); - fileUpload = InitFile(fstream, filename); + // Generate api parameter with filestream + using (var fstream = File.OpenRead(exportedFilePath)) + { + var fileUpload = InitFile(fstream, filename); // Make api call var result2 = _liftController.UploadLiftFile(proj2.Id, fileUpload).Result; - Assert.That(!(result is BadRequestObjectResult)); - - proj2 = _projServ.GetProject(proj2.Id).Result; - Assert.AreEqual(proj2.VernacularWritingSystem.Bcp47, roundTripContents.Language); + Assert.That(!(result2 is BadRequestObjectResult)); + } - fstream.Close(); + proj2 = _projServ.GetProject(proj2.Id).Result; + Assert.AreEqual(proj2.VernacularWritingSystem.Bcp47, roundTripContents.Language); - // Clean up zip file. - File.Delete(exportedFilePath); + // Clean up zip file. + File.Delete(exportedFilePath); - allWords = _wordrepo.GetAllWords(proj2.Id).Result; - Assert.AreEqual(allWords.Count, roundTripContents.NumOfWords); - // We are currently only testing guids on the single-entry data sets - if (roundTripContents.EntryGuid != "" && allWords.Count == 1) + allWords = _wordrepo.GetAllWords(proj2.Id).Result; + Assert.AreEqual(allWords.Count, roundTripContents.NumOfWords); + // We are currently only testing guids on the single-entry data sets + if (roundTripContents.EntryGuid != "" && allWords.Count == 1) + { + Assert.AreEqual(allWords[0].Guid.ToString(), roundTripContents.EntryGuid); + if (roundTripContents.SenseGuid != "") { - Assert.AreEqual(allWords[0].Guid.ToString(), roundTripContents.EntryGuid); - if (roundTripContents.SenseGuid != "") - { - Assert.AreEqual(allWords[0].Senses[0].Guid.ToString(), roundTripContents.SenseGuid); - } + Assert.AreEqual(allWords[0].Senses[0].Guid.ToString(), roundTripContents.SenseGuid); } + } - // Export - exportedFilePath = _liftController.CreateLiftExport(proj2.Id); - exportedDirectory = ExtractZipFile(exportedFilePath); + // Export + exportedFilePath = _liftController.CreateLiftExport(proj2.Id); + exportedDirectory = ExtractZipFile(exportedFilePath, null); - // Assert the file was created with desired hierarchy - Assert.That(Directory.Exists(exportedDirectory)); - Assert.That(Directory.Exists(Path.Combine(exportedDirectory, "Lift", "audio"))); - foreach (var audioFile in roundTripContents.AudioFiles) - { - var path = Path.Combine(exportedDirectory, "Lift", "audio", audioFile); - Assert.That(File.Exists(path), - $"The file {audioFile} can not be found at this path: {path}"); - } - Assert.That(Directory.Exists(Path.Combine(exportedDirectory, "Lift", "WritingSystems"))); - Assert.That(File.Exists(Path.Combine( - exportedDirectory, - "Lift", "WritingSystems", roundTripContents.Language + ".ldml"))); - Assert.That(File.Exists(Path.Combine(exportedDirectory, "Lift", "NewLiftFile.lift"))); - Directory.Delete(exportedDirectory, true); - - _wordrepo.DeleteAllWords(proj.Id); + // Assert the file was created with desired hierarchy + Assert.That(Directory.Exists(exportedDirectory)); + Assert.That(Directory.Exists(Path.Combine(exportedDirectory, "Lift", "audio"))); + foreach (var audioFile in roundTripContents.AudioFiles) + { + var path = Path.Combine(exportedDirectory, "Lift", "audio", audioFile); + Assert.That(File.Exists(path), + $"The file {audioFile} can not be found at this path: {path}"); } + Assert.That(Directory.Exists(Path.Combine(exportedDirectory, "Lift", "WritingSystems"))); + Assert.That(File.Exists(Path.Combine( + exportedDirectory, + "Lift", "WritingSystems", roundTripContents.Language + ".ldml"))); + Assert.That(File.Exists(Path.Combine(exportedDirectory, "Lift", "NewLiftFile.lift"))); + Directory.Delete(exportedDirectory, true); + + _wordrepo.DeleteAllWords(proj.Id); } } } diff --git a/Backend.Tests/Models/ProjectTests.cs b/Backend.Tests/Models/ProjectTests.cs index ae0011068d..7d70628032 100644 --- a/Backend.Tests/Models/ProjectTests.cs +++ b/Backend.Tests/Models/ProjectTests.cs @@ -31,6 +31,10 @@ public void TestNotEquals() project2.IsActive = !project.IsActive; Assert.IsFalse(project.Equals(project2)); + project2 = project.Clone(); + project2.LiftImported = !project.LiftImported; + Assert.IsFalse(project.Equals(project2)); + project2 = project.Clone(); project2.VernacularWritingSystem.Name = "different"; Assert.IsFalse(project.Equals(project2)); @@ -75,12 +79,24 @@ public void TestNotEquals() project2.InviteTokens.Add(new EmailInvite()); Assert.IsFalse(project.Equals(project2)); } + + [Test] + public void TestClone() + { + var system = new WritingSystem { Name = Name, Bcp47 = "en", Font = "calibri" }; + var project = new Project { Name = Name, VernacularWritingSystem = system }; + var domain = new SemanticDomain { Name = Name, Id = "1", Description = "text" }; + project.SemanticDomains.Add(domain); + var project2 = project.Clone(); + Assert.AreEqual(project, project2); + } } public class WritingSystemTests { private const string Name = "System 1"; private const string Bcp47 = "lang-1"; + private const string Font = "calibri"; [Test] public void TestEquals() @@ -110,5 +126,13 @@ public void TestToString() var systring = system.ToString(); Assert.IsTrue(systring.Contains(Name) && systring.Contains(Bcp47)); } + + [Test] + public void TestClone() + { + var system = new WritingSystem { Name = Name, Bcp47 = Bcp47, Font = Font }; + var clonedSystem = system.Clone(); + Assert.AreEqual(system, clonedSystem); + } } } diff --git a/Backend/Controllers/AudioController.cs b/Backend/Controllers/AudioController.cs index 62dcf489c9..312761ddc5 100644 --- a/Backend/Controllers/AudioController.cs +++ b/Backend/Controllers/AudioController.cs @@ -64,8 +64,8 @@ public async Task DownloadAudioFile(string projectId, string word } /// - /// Adds a pronunciation to a and saves locally to - /// ~/.CombineFiles/{ProjectId}/ExtractedLocation/Import/ExtractedLocation/Lift/audio + /// Adds a pronunciation to a and saves + /// locally to ~/.CombineFiles/{ProjectId}/Import/ExtractedLocation/Lift/audio /// /// POST: v1/projects/{projectId}/words/{wordId}/upload/audio /// Path to local audio file diff --git a/Backend/Controllers/LiftController.cs b/Backend/Controllers/LiftController.cs index a355dc6f7b..9b00260e6c 100644 --- a/Backend/Controllers/LiftController.cs +++ b/Backend/Controllers/LiftController.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using System.IO.Compression; using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; @@ -52,11 +51,10 @@ public async Task UploadLiftFile(string projectId, [FromForm] Fil return new UnsupportedMediaTypeResult(); } - // Ensure project exists - var project = _projectService.GetProject(projectId); - if (project is null) + // Ensure Lift file has not already been imported. + if (!_projectService.CanImportLift(projectId)) { - return new NotFoundObjectResult(projectId); + return new BadRequestObjectResult("A Lift file has already been uploaded"); } var file = fileUpload.File; @@ -71,39 +69,18 @@ public async Task UploadLiftFile(string projectId, [FromForm] Fil return new BadRequestObjectResult("Empty File"); } - // Get path to where we will copy the zip file - fileUpload.FilePath = GenerateFilePath( - FileType.Zip, - false, - "Compressed-Upload-" + $"{DateTime.Now:yyyy-MM-dd_hh-mm-ss-fff}", - Path.Combine(projectId, "Import")); - - // Copy file data to a new local file + // Copy zip file data to a new temporary file + fileUpload.FilePath = Path.GetTempFileName(); await using (var fs = new FileStream(fileUpload.FilePath, FileMode.OpenOrCreate)) { await file.CopyToAsync(fs); } - // Make destination for extracted files - var zipDest = Path.GetDirectoryName(fileUpload.FilePath); - if (zipDest is null) - { - throw new FileSystemError($"Could not get directory name of {fileUpload.FilePath}"); - } - - Directory.CreateDirectory(zipDest); - if (Directory.Exists(Path.Combine(zipDest, "ExtractedLocation"))) - { - return new BadRequestObjectResult("A file has already been uploaded"); - } - - // Extract the zip to new directory - var extractDir = Path.Combine(zipDest, "ExtractedLocation"); - Directory.CreateDirectory(extractDir); - ZipFile.ExtractToDirectory(fileUpload.FilePath, extractDir); + // Make temporary destination for extracted files + var extractDir = GetRandomTempDir(); - // Clean up the temporary zip file - System.IO.File.Delete(fileUpload.FilePath); + // Extract the zip to new created directory. + ExtractZipFile(fileUpload.FilePath, extractDir, true); // Check number of directories extracted var directoriesExtracted = Directory.GetDirectories(extractDir); @@ -148,22 +125,23 @@ public async Task UploadLiftFile(string projectId, [FromForm] Fil } } - // Get the directory and rename to be easier to reference elsewhere if needed - var correctPath = Path.Combine(extractDir, "Lift"); - if (!extractedDirPath.Equals(correctPath)) - { - Directory.Move(extractedDirPath, correctPath); - extractedDirPath = Path.Combine(extractDir, "Lift"); - } + // Copy the extracted contents into the persistent storage location for the project. + var liftStoragePath = GenerateFilePath( + FileType.Dir, + true, + "", + Path.Combine(projectId, "Import", "ExtractedLocation", "Lift")); + CopyDirectory(extractedDirPath, liftStoragePath); + Directory.Delete(extractDir, true); // Search for the lift file within the extracted files - var extractedLiftNameArr = Directory.GetFiles(extractedDirPath); + var extractedLiftNameArr = Directory.GetFiles(liftStoragePath); var extractedLiftPath = Array.FindAll(extractedLiftNameArr, x => x.EndsWith(".lift")); if (extractedLiftPath.Length > 1) { return new BadRequestObjectResult("More than one .lift file detected"); } - else if (extractedLiftPath.Length == 0) + if (extractedLiftPath.Length == 0) { return new BadRequestObjectResult("No lift files detected"); } @@ -180,9 +158,15 @@ public async Task UploadLiftFile(string projectId, [FromForm] Fil // Add character set to project from ldml file var proj = _projectService.GetProject(projectId).Result; _liftService.LdmlImport( - Path.Combine(extractedDirPath, "WritingSystems"), + Path.Combine(liftStoragePath, "WritingSystems"), proj.VernacularWritingSystem.Bcp47, _projectService, proj); + // Store that we have imported Lift data already for this project to signal the frontend + // not to attempt to import again. + var project = _projectService.GetProject(projectId).Result; + project.LiftImported = true; + await _projectService.Update(projectId, project); + return new ObjectResult(resp); } // If anything wrong happened, it's probably something wrong with the file itself diff --git a/Backend/Helper/FileUtilities.cs b/Backend/Helper/FileUtilities.cs index ded3e3520e..f4fb03af60 100644 --- a/Backend/Helper/FileUtilities.cs +++ b/Backend/Helper/FileUtilities.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.IO.Compression; using System.Linq; namespace BackendFramework.Helper @@ -60,7 +61,8 @@ public static string GenerateFilePath(FileType type, bool isDirectory, string cu Directory.CreateDirectory(returnFilepath); // If the path being generated is to a dir and not a file then don't add an extension - returnFilepath = Path.Combine(returnFilepath, customFileName + (isDirectory ? "" : FileTypeExtension(type))); + returnFilepath = Path.Combine( + returnFilepath, customFileName + (isDirectory ? "" : FileTypeExtension(type))); return returnFilepath; } @@ -107,6 +109,64 @@ private static string FileTypeExtension(FileType type) } } + /// + /// Create a randomly named directory in the system's temp folder, and get its name. + /// + /// Whether to create the directory. + /// Path to random temporary directory. + public static string GetRandomTempDir(bool create = true) + { + var tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + if (create) + { + Directory.CreateDirectory(tempDir); + } + return tempDir; + } + + /// + /// Extract a zip file to a new path, creating a unique temporary directory if requested. + /// + /// Path to zip file to extract. + /// + /// Directory path to create and extract zip file contents too. If null, a temporary directory is created. + /// + /// Whether to delete the zip file after extracting it. + /// The path to the extracted contents. + public static string ExtractZipFile(string zipFilePath, string? extractionDir, bool deleteZipFile = false) + { + if (extractionDir is null) + { + extractionDir = GetRandomTempDir(false); + } + + Directory.CreateDirectory(extractionDir); + ZipFile.ExtractToDirectory(zipFilePath, extractionDir); + if (deleteZipFile) + { + File.Delete(zipFilePath); + } + return extractionDir; + } + + /// + /// Recursively copies one directory into another. + /// + public static void CopyDirectory(string sourceDir, string targetDir) + { + Directory.CreateDirectory(targetDir); + + foreach (var file in Directory.GetFiles(sourceDir)) + { + File.Copy(file, Path.Combine(targetDir, Path.GetFileName(file))); + } + + foreach (var directory in Directory.GetDirectories(sourceDir)) + { + CopyDirectory(directory, Path.Combine(targetDir, Path.GetFileName(directory))); + } + } + public class DesktopNotFoundException : Exception { } diff --git a/Backend/Models/Project.cs b/Backend/Models/Project.cs index c20caf7a7b..9fb7e82f12 100644 --- a/Backend/Models/Project.cs +++ b/Backend/Models/Project.cs @@ -22,6 +22,9 @@ public class Project [BsonElement("isActive")] public bool IsActive { get; set; } + [BsonElement("liftImported")] + public bool LiftImported { get; set; } + [BsonElement("semanticDomains")] public List SemanticDomains { get; set; } @@ -60,6 +63,7 @@ public Project() Id = ""; Name = ""; IsActive = true; + LiftImported = false; AutocompleteSetting = AutocompleteSetting.On; VernacularWritingSystem = new WritingSystem(); AnalysisWritingSystems = new List(); @@ -79,6 +83,7 @@ public Project Clone() Id = (string)Id.Clone(), Name = (string)Name.Clone(), IsActive = IsActive, + LiftImported = LiftImported, AutocompleteSetting = AutocompleteSetting, VernacularWritingSystem = VernacularWritingSystem.Clone(), AnalysisWritingSystems = new List(), @@ -132,6 +137,7 @@ public bool ContentEquals(Project other) return other.Name.Equals(Name) && other.IsActive.Equals(IsActive) && + other.LiftImported.Equals(LiftImported) && other.AutocompleteSetting.Equals(AutocompleteSetting) && other.VernacularWritingSystem.Equals(VernacularWritingSystem) && @@ -175,6 +181,7 @@ public override int GetHashCode() var hash = new HashCode(); hash.Add(Id); hash.Add(Name); + hash.Add(LiftImported); hash.Add(IsActive); hash.Add(AutocompleteSetting); hash.Add(VernacularWritingSystem); diff --git a/Backend/Services/LiftApiServices.cs b/Backend/Services/LiftApiServices.cs index 3044054c07..6df84cabd4 100644 --- a/Backend/Services/LiftApiServices.cs +++ b/Backend/Services/LiftApiServices.cs @@ -563,7 +563,7 @@ public async void FinishEntry(LiftEntry entry) FileUtilities.FileType.Dir, false, "", Path.Combine(_projectId, "Import")); var extractedPathToImport = Path.Combine(importDir, "ExtractedLocation"); - // Get path to directory with audio files ~/{projectId}/Import/ExtractedLocation/{liftName}/audio + // Get path to directory with audio files ~/{projectId}/Import/ExtractedLocation/Lift/audio var importListArr = Directory.GetDirectories(extractedPathToImport); var extractedAudioDir = Path.Combine(importListArr.Single(), "audio"); @@ -574,7 +574,7 @@ public async void FinishEntry(LiftEntry entry) foreach (var pro in entry.Pronunciations) { // get path to audio file in lift package at - // ~/{projectId}/Import/ExtractedLocation/{liftName}/audio/{audioFile}.mp3 + // ~/{projectId}/Import/ExtractedLocation/Lift/audio/{audioFile}.mp3 var audioFile = pro.Media.First().Url; newWord.Audio.Add(audioFile); } diff --git a/Backend/Services/ProjectApiServices.cs b/Backend/Services/ProjectApiServices.cs index 4d17966a1e..d76720bca0 100644 --- a/Backend/Services/ProjectApiServices.cs +++ b/Backend/Services/ProjectApiServices.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.IO; using System.Threading.Tasks; using BackendFramework.Helper; using BackendFramework.Interfaces; @@ -77,6 +76,7 @@ public async Task Update(string projectId, Project project) var updateDef = Builders.Update .Set(x => x.Name, project.Name) .Set(x => x.IsActive, project.IsActive) + .Set(x => x.LiftImported, project.LiftImported) .Set(x => x.SemanticDomains, project.SemanticDomains) .Set(x => x.VernacularWritingSystem, project.VernacularWritingSystem) .Set(x => x.AnalysisWritingSystems, project.AnalysisWritingSystems) @@ -186,10 +186,8 @@ public async Task DuplicateCheck(string projectName) public bool CanImportLift(string projectId) { - var currentPath = FileUtilities.GenerateFilePath( - FileUtilities.FileType.Dir, true, "", Path.Combine(projectId, "Import")); - var zips = new List(Directory.GetFiles(currentPath, "*.zip")); - return zips.Count == 0; + var project = GetProject(projectId).Result; + return !project.LiftImported; } } } diff --git a/src/types/project.tsx b/src/types/project.tsx index 50c4cd5481..22560be4ba 100644 --- a/src/types/project.tsx +++ b/src/types/project.tsx @@ -1,6 +1,6 @@ import { randomIntString } from "../utilities"; import { AutoComplete } from "./AutoComplete"; -import { SemanticDomain, testWordList, Word } from "./word"; +import { SemanticDomain } from "./word"; export interface CustomField { name: string; @@ -15,6 +15,7 @@ export interface Project { id: string; name: string; isActive: boolean; + liftImported: boolean; semanticDomains: SemanticDomain[]; userRoles: string; vernacularWritingSystem: WritingSystem; @@ -23,7 +24,6 @@ export interface Project { rejectedCharacters: string[]; wordFields: string[]; partsOfSpeech: string[]; - words: Word[]; customFields: CustomField[]; autocompleteSetting: AutoComplete; } @@ -32,6 +32,7 @@ export const defaultProject = { id: "", name: "", isActive: true, + liftImported: false, semanticDomains: [], userRoles: "", vernacularWritingSystem: { name: "", bcp47: "", font: "" }, @@ -41,7 +42,6 @@ export const defaultProject = { customFields: [], wordFields: [], partsOfSpeech: [], - words: [], autocompleteSetting: AutoComplete.On, } as Project; @@ -51,6 +51,5 @@ export function randomProject(): Project { project.id = randomIntString(); project.name = randomIntString(); project.isActive = Math.random() < 0.5; - project.words = testWordList(); return project; }