From 8cd1dabd49dd26f6d030555acc45ba2c9af577f9 Mon Sep 17 00:00:00 2001 From: IRBorisov <8611739+IRBorisov@users.noreply.github.com> Date: Fri, 7 Jun 2024 20:43:26 +0300 Subject: [PATCH] Initial commit --- VBAMake.txt | 36 + VERSION | 1 + script/TestCommons.txt | 102 +++ script/TestVisio.txt | 68 ++ script/TestWord.txt | 53 ++ script/manifest.txt | 110 +++ skeleton/!Builder.xlsm | Bin 0 -> 39275 bytes skeleton/TestCommons.xlsm | Bin 0 -> 8389 bytes skeleton/TestVisio.vsdm | Bin 0 -> 46573 bytes skeleton/TestWord.docm | Bin 0 -> 19816 bytes src/builder/CB_AddModule.cls | 65 ++ src/builder/CB_AddProduct.cls | 43 ++ src/builder/CB_SharedModules.cls | 27 + src/builder/Declarations.bas | 213 ++++++ src/builder/DescriptorUI.cls | 13 + src/builder/DevHelper.bas | 25 + src/builder/InfoAction.cls | 183 +++++ src/builder/InfoBuild.cls | 20 + src/builder/InfoComponent.cls | 307 ++++++++ src/builder/InfoGlobals.cls | 86 +++ src/builder/InfoMakefile.cls | 182 +++++ src/builder/InfoManifest.cls | 413 +++++++++++ src/builder/InfoProduct.cls | 151 ++++ src/builder/ItemActionEnvironment.cls | 66 ++ src/builder/ItemVBReference.cls | 35 + src/builder/Main.bas | 671 ++++++++++++++++++ src/builder/MainImpl.bas | 234 ++++++ src/builder/database/DB_Components.cls | 51 ++ src/builder/database/DB_GlobalRefs.cls | 31 + src/builder/database/DB_Products.cls | 61 ++ src/builder/database/DB_SharedModules.cls | 46 ++ src/builder/database/DB_Tests.cls | 41 ++ src/builder/database/DataAccess.bas | 101 +++ src/builder/database/IteratorComponent.cls | 209 ++++++ src/builder/database/IteratorProduct.cls | 220 ++++++ src/builder/database/IteratorSharedModule.cls | 105 +++ src/builder/database/IteratorTest.cls | 118 +++ src/builder/z_UIMessages.bas | 129 ++++ src/builder/z_UIRibbon.bas | 62 ++ src/commons/Declarations.bas | 4 + src/commons/DevHelper.bas | 59 ++ src/commons/Main.bas | 3 + src/commons/MainImpl.bas | 4 + src/test/TestCustomObject.cls | 20 + src/test/s_CompoundIntervals.cls | 119 ++++ src/test/s_Config.cls | 105 +++ src/test/s_ExCollection.cls | 237 +++++++ src/test/s_ExColor.cls | 62 ++ src/test/s_ExHash.cls | 39 + src/test/s_ExVBA.cls | 645 +++++++++++++++++ src/test/s_ExWinAPI.cls | 341 +++++++++ src/test/s_Factorizator.cls | 68 ++ src/test/s_Graph.cls | 414 +++++++++++ src/test/s_JSON.cls | 212 ++++++ src/test/s_Logger.cls | 119 ++++ src/test/s_ParseDate.cls | 160 +++++ src/test/s_Path.cls | 209 ++++++ src/test/s_StaticHierarchy.cls | 124 ++++ src/test/s_TextEdit.cls | 134 ++++ src/test/s_UndoWrapper.cls | 98 +++ src/test/s_VsoExtension.cls | 34 + src/test/s_VsoGraph.cls | 89 +++ src/test/s_VsoUtilities.cls | 236 ++++++ src/test/s_VsoWrapper.cls | 98 +++ src/test/s_WordWrapper.cls | 611 ++++++++++++++++ src/test/s_XLWrapper.cls | 100 +++ src/visio/Declarations.bas | 8 + src/visio/DevHelper.bas | 25 + src/visio/Main.bas | 7 + src/visio/MainImpl.bas | 4 + src/word/Declarations.bas | 4 + src/word/DevHelper.bas | 20 + src/word/Main.bas | 3 + src/word/MainImpl.bas | 4 + test/TestInvalid.docx | Bin 0 -> 8381 bytes test/TestWord.docx | Bin 0 -> 19281 bytes test/testTemplate.dotx | Bin 0 -> 21524 bytes ui/.rels | 2 + ui/customUI.xml | 199 ++++++ 79 files changed, 8898 insertions(+) create mode 100644 VBAMake.txt create mode 100644 VERSION create mode 100644 script/TestCommons.txt create mode 100644 script/TestVisio.txt create mode 100644 script/TestWord.txt create mode 100644 script/manifest.txt create mode 100644 skeleton/!Builder.xlsm create mode 100644 skeleton/TestCommons.xlsm create mode 100644 skeleton/TestVisio.vsdm create mode 100644 skeleton/TestWord.docm create mode 100644 src/builder/CB_AddModule.cls create mode 100644 src/builder/CB_AddProduct.cls create mode 100644 src/builder/CB_SharedModules.cls create mode 100644 src/builder/Declarations.bas create mode 100644 src/builder/DescriptorUI.cls create mode 100644 src/builder/DevHelper.bas create mode 100644 src/builder/InfoAction.cls create mode 100644 src/builder/InfoBuild.cls create mode 100644 src/builder/InfoComponent.cls create mode 100644 src/builder/InfoGlobals.cls create mode 100644 src/builder/InfoMakefile.cls create mode 100644 src/builder/InfoManifest.cls create mode 100644 src/builder/InfoProduct.cls create mode 100644 src/builder/ItemActionEnvironment.cls create mode 100644 src/builder/ItemVBReference.cls create mode 100644 src/builder/Main.bas create mode 100644 src/builder/MainImpl.bas create mode 100644 src/builder/database/DB_Components.cls create mode 100644 src/builder/database/DB_GlobalRefs.cls create mode 100644 src/builder/database/DB_Products.cls create mode 100644 src/builder/database/DB_SharedModules.cls create mode 100644 src/builder/database/DB_Tests.cls create mode 100644 src/builder/database/DataAccess.bas create mode 100644 src/builder/database/IteratorComponent.cls create mode 100644 src/builder/database/IteratorProduct.cls create mode 100644 src/builder/database/IteratorSharedModule.cls create mode 100644 src/builder/database/IteratorTest.cls create mode 100644 src/builder/z_UIMessages.bas create mode 100644 src/builder/z_UIRibbon.bas create mode 100644 src/commons/Declarations.bas create mode 100644 src/commons/DevHelper.bas create mode 100644 src/commons/Main.bas create mode 100644 src/commons/MainImpl.bas create mode 100644 src/test/TestCustomObject.cls create mode 100644 src/test/s_CompoundIntervals.cls create mode 100644 src/test/s_Config.cls create mode 100644 src/test/s_ExCollection.cls create mode 100644 src/test/s_ExColor.cls create mode 100644 src/test/s_ExHash.cls create mode 100644 src/test/s_ExVBA.cls create mode 100644 src/test/s_ExWinAPI.cls create mode 100644 src/test/s_Factorizator.cls create mode 100644 src/test/s_Graph.cls create mode 100644 src/test/s_JSON.cls create mode 100644 src/test/s_Logger.cls create mode 100644 src/test/s_ParseDate.cls create mode 100644 src/test/s_Path.cls create mode 100644 src/test/s_StaticHierarchy.cls create mode 100644 src/test/s_TextEdit.cls create mode 100644 src/test/s_UndoWrapper.cls create mode 100644 src/test/s_VsoExtension.cls create mode 100644 src/test/s_VsoGraph.cls create mode 100644 src/test/s_VsoUtilities.cls create mode 100644 src/test/s_VsoWrapper.cls create mode 100644 src/test/s_WordWrapper.cls create mode 100644 src/test/s_XLWrapper.cls create mode 100644 src/visio/Declarations.bas create mode 100644 src/visio/DevHelper.bas create mode 100644 src/visio/Main.bas create mode 100644 src/visio/MainImpl.bas create mode 100644 src/word/Declarations.bas create mode 100644 src/word/DevHelper.bas create mode 100644 src/word/Main.bas create mode 100644 src/word/MainImpl.bas create mode 100644 test/TestInvalid.docx create mode 100644 test/TestWord.docx create mode 100644 test/testTemplate.dotx create mode 100644 ui/.rels create mode 100644 ui/customUI.xml diff --git a/VBAMake.txt b/VBAMake.txt new file mode 100644 index 0000000..2d993da --- /dev/null +++ b/VBAMake.txt @@ -0,0 +1,36 @@ +# == Properties Section == +# configuration properties +# use .ini format to define properties +# mandatory properties: name, artifact_home, source_home + +id = VBABuilder +name = VBABuilder +description = Система сборки и тестирования VBA +artifact_home = VBABuilder +source_home = VBABuilder +install_home = \\fs1.concept.ru\projects\10 Автоматизация деятельности\01 Высокие технологии\VBABuilder + +%% +# === Build section === +# Available commands: +# build LOCAL_MANIFEST +# copy LOCAL_SOURCE -> [LOCAL_ARTIFACT] +# save_as LOCAL_ARTIFACT -> LOCAL_ARTIFACT +# run LOCAL_SOURCE.bat + +build script\manifest.txt +build script\TestCommons.txt +build script\TestVisio.txt +build script\TestWord.txt + +copy test + +%% +# === Install section == +# Available commands: +# install LOCAL_ARTIFACT -> [INSTALL_PATH] +# add_template LOCAL_ARTIFACT -> [LOCAL_TEMPLATE] +# run LOCAL_ARTIFACT.bat <- [PARAMETERS] +# run APPLICATION <- [PARAMETERS] + +install !Builder.xlsm \ No newline at end of file diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..6085e94 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.2.1 diff --git a/script/TestCommons.txt b/script/TestCommons.txt new file mode 100644 index 0000000..48386c6 --- /dev/null +++ b/script/TestCommons.txt @@ -0,0 +1,102 @@ +# == Properties Section == +# configuration properties +# use .ini format to define properties +# mandatory properties: name, artifact + +name = TestCommons.xlsm +artifact = TestCommons.xlsm + +%% +# === Imports Section === +# Hierarchy of folders and files +# Use Tabulator to mark next level in hierarchy +# All folders are nested into SharedHome path + +api + ex_WinAPI.bas + API_Project.cls + API_VsoWrapper.cls + API_WordWrapper.cls + API_XLWrapper.cls + API_Path.cls + +parsers + z_ParserRegex.bas + ParserDate.cls + +utility + ex_VBA.bas + ex_Hash.bas + ex_Collection.bas + ex_Regex.bas + ex_Color.bas + + CDS_Edge.cls + CDS_Graph.cls + CDS_Node.cls + CDS_NodeSH.cls + CDS_StaticHierarchy.cls + CDS_Interval.cls + CDS_CompoundIntervals.cls + API_LinkedComponents.cls + API_StrongComponents.cls + API_GraphOrdering.cls + API_JSON.cls + API_Config.cls + +dev + DevTester.bas + API_Logger.cls + +%% +# === Source Code Section == +# Hierarchy of folders and files +# Use Tabulator to mark next level in hierarchy +# All folders are nested into SourceHome path + +src + commons + DevHelper.bas + Declarations.bas + Main.bas + MainImpl.bas + + test + TestCustomObject.cls + s_ExCollection.cls + s_ExVBA.cls + s_ExHash.cls + s_ExWinAPI.cls + s_ParseDate.cls + s_Logger.cls + s_XLWrapper.cls + s_VsoWrapper.cls + s_WordWrapper.cls + s_Graph.cls + s_StaticHierarchy.cls + s_ExColor.cls + s_CompoundIntervals.cls + s_Path.cls + s_JSON.cls + s_Config.cls + +%% +# ===== UI Section ======= +# Pairs of path to UI elements, use " -> " delimiter +# First component is a path relative to SourceHome\ui folders +# Second component is internal path inside project file + +%% +# === References Section === +# List dependencies in one of the formats +# global : GLOBAL_NAME +# guid : {REGISTERED_GUID} +# file : PATH_TO_LIBRARY + +global : VBScript_RegExp_55 +global : Scripting +global : Visio +global : Word +global : Shell32 +global : VBIDE +global : ADODB \ No newline at end of file diff --git a/script/TestVisio.txt b/script/TestVisio.txt new file mode 100644 index 0000000..5ea5255 --- /dev/null +++ b/script/TestVisio.txt @@ -0,0 +1,68 @@ +# == Properties Section == +# configuration properties +# use .ini format to define properties +# mandatory properties: name, artifact + +name = TestVisio.vsdm +artifact = TestVisio.vsdm + +%% +# === Imports Section === +# Hierarchy of folders and files +# Use Tabulator to mark next level in hierarchy +# All folders are nested into SharedHome path + +dev + DevTester.bas + +api + API_VsoWrapper.cls + +utility + ex_VBA.bas + CDS_Edge.cls + CDS_Factorizator.cls + CDS_Graph.cls + CDS_Node.cls + +visio + z_CCVsoExtension.bas + z_VsoGraph.bas + z_VsoUtilities.bas + API_UndoWrapper.cls + + +%% +# === Source Code Section == +# Hierarchy of folders and files +# Use Tabulator to mark next level in hierarchy +# All folders are nested into SourceHome path + +src + visio + DevHelper.bas + Declarations.bas + Main.bas + MainImpl.bas + + test + s_UndoWrapper.cls + s_Factorizator.cls + s_VsoUtilities.cls + s_VsoExtension.cls + s_VsoGraph.cls + +%% +# ===== UI Section ======= +# Pairs of path to UI elements, use " -> " delimiter +# First component is a path relative to SourceHome\ui folders +# Second component is internal path inside project file + +%% +# === References Section === +# List dependencies in one of the formats +# global : GLOBAL_NAME +# guid : {REGISTERED_GUID} +# file : PATH_TO_LIBRARY + +global : Scripting \ No newline at end of file diff --git a/script/TestWord.txt b/script/TestWord.txt new file mode 100644 index 0000000..6abb00c --- /dev/null +++ b/script/TestWord.txt @@ -0,0 +1,53 @@ +# == Properties Section == +# configuration properties +# use .ini format to define properties +# mandatory properties: name, artifact + +name = TestWord.docm +artifact = TestWord.docm + +%% +# === Imports Section === +# Hierarchy of folders and files +# Use Tabulator to mark next level in hierarchy +# All folders are nested into SharedHome path + +dev + DevTester.bas + +word + ex_Word.bas + +utility + ex_VBA.bas + +%% +# === Source Code Section == +# Hierarchy of folders and files +# Use Tabulator to mark next level in hierarchy +# All folders are nested into SourceHome path + +src + word + DevHelper.bas + Declarations.bas + Main.bas + MainImpl.bas + + test + s_TextEdit.cls + +%% +# ===== UI Section ======= +# Pairs of path to UI elements, use " -> " delimiter +# First component is a path relative to SourceHome\ui folders +# Second component is internal path inside project file + +%% +# === References Section === +# List dependencies in one of the formats +# global : GLOBAL_NAME +# guid : {REGISTERED_GUID} +# file : PATH_TO_LIBRARY + +global : Scripting \ No newline at end of file diff --git a/script/manifest.txt b/script/manifest.txt new file mode 100644 index 0000000..033a89e --- /dev/null +++ b/script/manifest.txt @@ -0,0 +1,110 @@ +# == Properties Section == +# configuration properties +# use .ini format to define properties +# mandatory properties: name, artifact + +name = !Builder.xlsm +artifact = !Builder.xlsm + +%% +# === Imports Section === +# Hierarchy of folders and files +# Use Tabulator to mark next level in hierarchy +# All folders are nested into SharedHome path + +dev + DevTools.bas + DevTester.bas + + CDS_InfoTests.cls + CDS_InfoFunction.cls + API_Logger.cls + API_TestRunner.cls + +api + ex_WinAPI.bas + API_Path.cls + API_Project.cls + API_VsoWrapper.cls + API_WordWrapper.cls + API_XLWrapper.cls + API_UserInteraction.cls + +excel + ex_Excel.bas + +utility + ex_VBA.bas + ex_Collection.bas + ex_DataPreparation.bas + ex_Version.bas + + API_Timer.cls + API_JSON.cls + API_DistrManifest.cls + +%% +# === Source Code Section == +# Hierarchy of folders and files +# Use Tabulator to mark next level in hierarchy +# All folders are nested into SourceHome path + +src + builder + DevHelper.bas + Declarations.bas + Main.bas + MainImpl.bas + z_UIRibbon.bas + z_UIMessages.bas + + CB_SharedModules.cls + CB_AddModule.cls + CB_AddProduct.cls + DescriptorUI.cls + InfoComponent.cls + InfoManifest.cls + InfoMakefile.cls + InfoProduct.cls + InfoAction.cls + InfoBuild.cls + InfoGlobals.cls + ItemActionEnvironment.cls + ItemVBReference.cls + + database + DataAccess.bas + DB_SharedModules.cls + DB_Products.cls + DB_Components.cls + DB_GlobalRefs.cls + DB_Tests.cls + IteratorSharedModule.cls + IteratorTest.cls + IteratorComponent.cls + IteratorProduct.cls + +%% +# ===== UI Section ======= +# Pairs of path to UI elements, use " -> " delimiter +# First component is a path relative to SourceHome\ui folders +# Second component is internal path inside project file + +.rels -> _rels\.rels +customUI.xml -> customUI\customUI.xml + +%% +# === References Section === +# List dependencies in one of the formats +# global : GLOBAL_NAME +# guid : {REGISTERED_GUID} +# file : PATH_TO_LIBRARY + +global : VBIDE +global : Shell32 +global : Scripting +global : ADODB +global : Visio +global : Word +global : MSForms +global : IWshRuntimeLibrary \ No newline at end of file diff --git a/skeleton/!Builder.xlsm b/skeleton/!Builder.xlsm new file mode 100644 index 0000000000000000000000000000000000000000..ccc29c4e36f04abcef18e6dc61455a148ecec852 GIT binary patch literal 39275 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%0DAfxBoO2%By8dz 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 S009vKev$xFaeu;J@BSa_zHK1@ literal 0 HcmV?d00001 diff --git a/skeleton/TestCommons.xlsm b/skeleton/TestCommons.xlsm new file mode 100644 index 0000000000000000000000000000000000000000..8b4267a7f9e0b1d4962365300b64197cbdeb4ae1 GIT binary patch literal 8389 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 literal 0 HcmV?d00001 diff --git a/skeleton/TestVisio.vsdm b/skeleton/TestVisio.vsdm new file mode 100644 index 0000000000000000000000000000000000000000..ac70ce96f97298def5b2bc9f6429a7a1219c3433 GIT binary patch literal 46573 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 literal 0 HcmV?d00001 diff --git a/src/builder/CB_AddModule.cls b/src/builder/CB_AddModule.cls new file mode 100644 index 0000000..764f5b2 --- /dev/null +++ b/src/builder/CB_AddModule.cls @@ -0,0 +1,65 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "CB_AddModule" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +' Callback adding shared module from file +Option Explicit + +Private iOut_ As IteratorSharedModule +Private tester_ As VBIDE.VBProject +Private base_ As String +Private fso_ As Scripting.FileSystemObject + +Private Type ProcedureDescriptor + name_ As String + type_ As VBIDE.vbext_ProcKind +End Type + +Public Function Init(iOut As IteratorSharedModule, oTestProject As VBIDE.VBProject, sBasePath$) + Set iOut_ = iOut + base_ = sBasePath + Set tester_ = oTestProject + Set fso_ = New Scripting.FileSystemObject +End Function + +Public Function TryAddModule(aFile As Scripting.File) + If Not IsVBAModuleExtension(fso_.GetExtensionName(aFile.Name)) Then _ + Exit Function + + Dim sPath$: sPath = aFile.Path + Dim aComp As VBIDE.VBComponent + Set aComp = tester_.VBComponents.Import(sPath) + + With iOut_ + .Name = aComp.Name + .ModuleType = aComp.Type + .Path = VBA.Mid(sPath, Len(base_) + 2, VBA.Len(sPath) - VBA.Len(base_) - 2 - VBA.Len(aFile.Name)) + .Version = ExtractVersion(aComp.CodeModule) + .LoC = aComp.CodeModule.CountOfLines + .Declarations = aComp.CodeModule.CountOfDeclarationLines + .CountAPI = Dev_CountPublicAPI(aComp.CodeModule) + + Call .Increment + End With + + Call tester_.VBComponents.Remove(aComp) +End Function + +' ===== +Private Function ExtractVersion(target As VBIDE.CodeModule) As String + Const VERSION_PREFIX$ = "module version: " + Dim nLine& + For nLine = 1 To target.CountOfLines Step 1 + Dim sTxt$: sTxt = target.Lines(nLine, 1) + Dim nPrefix&: nPrefix = InStr(1, sTxt, VERSION_PREFIX) + If nPrefix <> 0 Then + ExtractVersion = Right(sTxt, Len(sTxt) - nPrefix + 1 - Len(VERSION_PREFIX)) + Exit Function + End If + Next nLine +End Function diff --git a/src/builder/CB_AddProduct.cls b/src/builder/CB_AddProduct.cls new file mode 100644 index 0000000..425cca6 --- /dev/null +++ b/src/builder/CB_AddProduct.cls @@ -0,0 +1,43 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "CB_AddProduct" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +' Callback adding shared module from file +Option Explicit + +Private iProducts_ As DB_Products +Private fso_ As Scripting.FileSystemObject + +Public newCount_ As Long + +Public Function Init(iProducts As DB_Products) + newCount_ = 0 + Set iProducts_ = iProducts + Set fso_ = New Scripting.FileSystemObject +End Function + +Public Function ScanFolder(iFolder As Scripting.Folder) As Boolean +' Callback should return TRUE to continue recursive + Dim sMakefile$: sMakefile = fso_.BuildPath(iFolder, MAKEFILE_NAME) + ScanFolder = Not fso_.FileExists(sMakefile) + If ScanFolder Then _ + Exit Function + + Dim iMake As New InfoMakefile + If Not iMake.Init(sMakefile) Then _ + Exit Function + + If iProducts_.Contains(iMake.ProductName) Then + Dim iProduct As IteratorProduct: Set iProduct = iProducts_.Access(iMake.ProductName) + If VBA.UCase(iProduct.SourcePath) = VBA.UCase(iFolder.Path) Then _ + Call iProduct.SyncMakefile(iMake) + Else + Call iProducts_.Insert(iMake) + newCount_ = newCount_ + 1 + End If +End Function diff --git a/src/builder/CB_SharedModules.cls b/src/builder/CB_SharedModules.cls new file mode 100644 index 0000000..416422b --- /dev/null +++ b/src/builder/CB_SharedModules.cls @@ -0,0 +1,27 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "CB_SharedModules" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Public data_ As Scripting.Dictionary +Private fso_ As Scripting.FileSystemObject + +Public Function Init() + Set data_ = New Scripting.Dictionary + Set fso_ = New Scripting.FileSystemObject +End Function + +Public Function ProcessFile(aFile As Scripting.File) + If Not IsVBAModuleExtension(fso_.GetExtensionName(aFile.Name)) Then _ + Exit Function + Dim sKey$: sKey = fso_.GetBaseName(aFile.Name) + If data_.Exists(sKey) Then _ + Exit Function + Call data_.Add(sKey, aFile.Path) +End Function diff --git a/src/builder/Declarations.bas b/src/builder/Declarations.bas new file mode 100644 index 0000000..fb011f1 --- /dev/null +++ b/src/builder/Declarations.bas @@ -0,0 +1,213 @@ +Attribute VB_Name = "Declarations" +Option Private Module +Option Explicit + +Public Const SERVER_PATH_CIHT = "\\fs1.concept.ru\projects\04 \60 " +Public Const SERVER_PATH_AUTOMATION = "\\fs1.concept.ru\projects\10 " + +' ========= Parameters ======= +Public Const MAKEFILE_NAME = "VBAMake.txt" +Public Const LOC_MINIMUM = 3 ' Minimum ammount of lines of code required to be listed in Modules + +Public Const FIRST_ROW = 2 + +Public Const TABLE_HEAD_PRODUCT = "ProductName" + +Public Const SUBFOLDER_SKELETON = "skeleton" +Public Const SUBFOLDER_SCRIPT = "script" +Public Const SUBFOLDER_UI = "ui" +Public Const SUBFOLDER_VERSION_ARCHIVE = "!Versions" + +Public Const SHEET_PRODUCTS = "Products" +Public Const SHEET_COMPONENTS = "Components" +Public Const SHEET_SHARED = "Shared" +Public Const SHEET_TESTS = "Tests" +Public Const SHEET_PARAMETERS = "Params" + +Public Const MANIFEST_PROPS_NAME = "name" +Public Const MANIFEST_PROPS_ARTIFACT = "artifact" + +Public Const MAKEFILE_PROPS_ID = "id" +Public Const MAKEFILE_PROPS_NAME = "name" +Public Const MAKEFILE_PROPS_DESCRIPTION = "description" +Public Const MAKEFILE_PROPS_ARTIFACT = "artifact_home" +Public Const MAKEFILE_PROPS_SOURCE = "source_home" +Public Const MAKEFILE_PROPS_INSTALL = "install_home" + +Public Enum TVBReference + T_REF_UNDEF = 0 + [_First] = 1 + + T_REF_GLOBAL_NAME = 1 + T_REF_GUID = 2 + T_REF_FILE = 3 + + [_Last] = 3 +End Enum + +Public Enum TBuildAction + T_ACT_UNDEF = 0 + [_First] = 1 + + T_ACT_BUILD = 1 + T_ACT_COPY = 2 + T_ACT_SAVE_AS = 3 + T_ACT_RUN = 4 + T_ACT_INSTALL = 5 + T_ACT_TEMPLATE = 6 + + [_Last] = 6 +End Enum + +' Domain for local paths +Public Enum TPathDomain + T_PD_UNDEF = 0 + [_First] = 1 + + T_PD_SOURCE = 1 + T_PD_ARTIFACT = 2 + T_PD_GLOBAL = 3 + + [_Last] = 3 +End Enum + +Public Enum TBuildStatus + T_BS_PENDING = 0 + T_BS_OK = 1 + T_BS_FAILED = 2 +End Enum + +Public Enum TBuildType + T_BUILD_DEBUG = 0 + T_BUILD_RELEASE = 1 +End Enum + +' _R_ - reference attributes +Public Enum RefsStruct + [_First] = 1 + + S_R_NAME = 1 + S_R_TYPE = 2 + S_R_FILE = 3 + S_R_GUID = 4 + S_R_BROKEN = 5 + S_R_MAJOR = 6 + S_R_MINOR = 7 + S_R_DESCRIPTION = 8 + + [_Last] = 8 +End Enum + +' _M_ - module attributes +Public Enum ModuleStruct + [_First] = 1 + + S_M_NAME = 1 + S_M_TYPE = 2 + S_M_SHARED = 3 + S_M_LINES_TOTAL = 4 + S_M_LINES_DECLARATIONS = 5 + S_M_COUNT_API = 6 + + [_Last] = 6 +End Enum + +' _S_ - shared module attributes +Public Enum SharedStruct + [_First] = 1 + + S_S_NAME = 1 + S_S_TYPE = 2 + S_S_PATH = 3 + S_S_VERSION = 4 + S_S_LINES_TOTAL = 5 + S_S_LINES_DECLARATIONS = 6 + S_S_API_COUNT = 7 + S_S_USAGE = 8 + + [_Last] = 8 +End Enum + +' _T_ - tests module attributes +Public Enum TestStruct + [_First] = 1 + + S_T_PRODUCT = 1 + S_T_COMPONENT = 2 + S_T_SUITE = 3 + S_T_TEST = 4 + S_T_DATE = 5 + S_T_DURATION = 6 + S_T_STATUS = 7 + S_T_MESSAGE = 8 + + [_Last] = 8 +End Enum + +' _G_ - global references +Public Enum GlobalRefsStruct + [_First] = 1 + + S_G_NAME_ID = 1 + S_G_GUID = 2 + S_G_MINOR = 3 + S_G_MAJOR = 4 + S_G_FULL_NAME = 5 + + [_Last] = 5 +End Enum + +' ========== Conversion API ======== +Public Function ActionType2Text(aType As TBuildAction) As String + Select Case aType + Case T_ACT_BUILD: ActionType2Text = "build" + Case T_ACT_COPY: ActionType2Text = "copy" + Case T_ACT_SAVE_AS: ActionType2Text = "save_as" + Case T_ACT_RUN: ActionType2Text = "run" + Case T_ACT_INSTALL: ActionType2Text = "install" + Case T_ACT_TEMPLATE: ActionType2Text = "add_template" + Case Else: ActionType2Text = "UNDEF" + End Select +End Function + +Public Function Text2ActionType(sText$) As TBuildAction + Select Case sText + Case "build": Text2ActionType = T_ACT_BUILD + Case "copy": Text2ActionType = T_ACT_COPY + Case "save_as": Text2ActionType = T_ACT_SAVE_AS + Case "run": Text2ActionType = T_ACT_RUN + Case "install": Text2ActionType = T_ACT_INSTALL + Case "add_template": Text2ActionType = T_ACT_TEMPLATE + Case Else: Text2ActionType = "UNDEF" + End Select +End Function + +Public Function RefTypeFrom(sTypeText$) As TVBReference + Select Case sTypeText + Case "global": RefTypeFrom = T_REF_GLOBAL_NAME + Case "guid": RefTypeFrom = T_REF_GUID + Case "file": RefTypeFrom = T_REF_FILE + Case Else: RefTypeFrom = T_REF_UNDEF + End Select +End Function + +Public Function RefTypeToString(iType As TVBReference) As String + Select Case iType + Case T_REF_GLOBAL_NAME: RefTypeToString = "global" + Case T_REF_GUID: RefTypeToString = "guid" + Case T_REF_FILE: RefTypeToString = "file" + Case Else: RefTypeToString = "UNDEF" + End Select +End Function + +Public Function BuildDescription(bType As TBuildType) As String + Select Case bType + Case T_BUILD_DEBUG: BuildDescription = "Debug" + Case T_BUILD_RELEASE: BuildDescription = "Release" + Case Else: BuildDescription = "UNKNOWN" + End Select +End Function + +Public Function IsVBAModuleExtension(sExt$) As Boolean + IsVBAModuleExtension = sExt = "cls" Or sExt = "frm" Or sExt = "bas" +End Function diff --git a/src/builder/DescriptorUI.cls b/src/builder/DescriptorUI.cls new file mode 100644 index 0000000..27b0220 --- /dev/null +++ b/src/builder/DescriptorUI.cls @@ -0,0 +1,13 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "DescriptorUI" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Public sourcePath_ As String +Public internalPath_ As String diff --git a/src/builder/DevHelper.bas b/src/builder/DevHelper.bas new file mode 100644 index 0000000..f0d91da --- /dev/null +++ b/src/builder/DevHelper.bas @@ -0,0 +1,25 @@ +Attribute VB_Name = "DevHelper" +Option Private Module +Option Explicit + +Public Function Dev_PrepareSkeleton() + Call ClearShared + Call ClearTests + + Call ThisWorkbook.Sheets(SHEET_COMPONENTS).UsedRange.Offset(1, 0).ClearContents + Call ThisWorkbook.Sheets(SHEET_PRODUCTS).UsedRange.Offset(1, 0).ClearContents +End Function + +Public Function Dev_ManualRunTest() + Dim sSuite$: sSuite = "s_UndoWrapper" + Dim sTest$: sTest = "t_BasicUndo" + Dim sMsg$: sMsg = Dev_RunTestDebug(sSuite, sTest) + Debug.Print sMsg + Call MsgBox(sMsg) +End Function + +Public Function Dev_GetTestSuite(sName$) As Object + Select Case sName + ' Case "s_UndoWrapper": Set Dev_GetTestSuite = New s_UndoWrapper + End Select +End Function diff --git a/src/builder/InfoAction.cls b/src/builder/InfoAction.cls new file mode 100644 index 0000000..76f7739 --- /dev/null +++ b/src/builder/InfoAction.cls @@ -0,0 +1,183 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "InfoAction" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Public type_ As TBuildAction +Public args_ As Collection + +Private Sub Class_Initialize() + Set args_ = New Collection +End Sub + +' ===== Properties ===== +Public Property Get Description() As String + Description = ActionType2Text(type_) + If args_.Count > 0 Then _ + Description = Description & " " & args_(1) + If args_.Count > 1 Then _ + Description = Description & " -> " & args_(2) +End Property + +Public Property Get IsBuildOnly() As Boolean + Select Case type_ + Case T_ACT_BUILD: IsBuildOnly = True + Case T_ACT_COPY: IsBuildOnly = True + Case T_ACT_SAVE_AS: IsBuildOnly = True + Case T_ACT_RUN: IsBuildOnly = False + Case T_ACT_INSTALL: IsBuildOnly = False + Case T_ACT_TEMPLATE: IsBuildOnly = False + Case Else: IsBuildOnly = False + End Select +End Property + +Public Property Get IsInstallOnly() As Boolean + Select Case type_ + Case T_ACT_BUILD: IsInstallOnly = False + Case T_ACT_COPY: IsInstallOnly = False + Case T_ACT_SAVE_AS: IsInstallOnly = False + Case T_ACT_RUN: IsInstallOnly = False + Case T_ACT_INSTALL: IsInstallOnly = True + Case T_ACT_TEMPLATE: IsInstallOnly = True + Case Else: IsInstallOnly = False + End Select +End Property + +' ===== Actions ===== +Public Function Execute(env As ItemActionEnvironment) As Boolean + Select Case type_ + Case T_ACT_BUILD: Execute = ExecuteBuild(env) + Case T_ACT_COPY: Execute = ExecuteCopy(env) + Case T_ACT_SAVE_AS: Execute = ExecuteSaveAs(env) + Case T_ACT_RUN: Execute = ExecuteRun(env) + Case T_ACT_INSTALL: Execute = ExecuteInstall(env) + Case T_ACT_TEMPLATE: Execute = ExecuteTemplate(env) + Case Else: Execute = False + End Select +End Function + +' ====== +Private Function ExecuteBuild(env As ItemActionEnvironment) As Boolean + ExecuteBuild = False + + Dim sManifest$: sManifest = CPath(args_(1)).ToGlobal(env.sourceHome_).Text + Dim iComponent As New InfoComponent + If Not iComponent.Init(sManifest, env.sourceHome_, env.artifactHome_, env.productVersion_) Then + Call env.reporter_.Log("Failed loading component manifest: " & sManifest) + Exit Function + End If + + Dim iData As IteratorComponent: Set iData = env.components_.Access(env.product_, iComponent.ItemName) + If iData Is Nothing Then + Call env.reporter_.Log("Failed accessing component: " & sManifest) + Exit Function + End If + + Dim testOut As IteratorTest: Set testOut = env.testDB_.OutputFor(env.product_, iComponent.ItemName) + Dim buildRes As InfoBuild: Set buildRes = iComponent.Build(env.buildType_, env.reporter_, testOut) + Call testOut.RemoveRow + + ExecuteBuild = buildRes.status_ = T_BS_OK + Call iData.SyncBuildInfo(buildRes) +End Function + +Private Function ExecuteCopy(env As ItemActionEnvironment) As Boolean + Dim sSource$: sSource = CPath(args_(1)).ToGlobal(env.sourceHome_).Text + Dim sDestination$ + If args_.Count < 2 Then + sDestination = env.artifactHome_ & "\" & env.fso_.GetFileName(sSource) + Else + sDestination = CPath(args_(2)).ToGlobal(env.artifactHome_).Text + End If + ExecuteCopy = CopyFileOrFolder(sSource, sDestination, env.fso_) +End Function + +Private Function ExecuteSaveAs(env As ItemActionEnvironment) As Boolean + ExecuteSaveAs = False + + Dim iSource As API_Path: Set iSource = CPath(args_(1)).ToGlobal(env.artifactHome_) + Dim iDestination As API_Path: Set iDestination = CPath(args_(2)).ToGlobal(env.artifactHome_) + If iSource.Extension = iDestination.Extension Then + Call CopyFileOrFolder(iSource.Text, iDestination.Text, env.fso_) + Else + Dim aWrap As Object: Set aWrap = AccessArtifact(iSource.Text, bReadOnly:=True) + If aWrap Is Nothing Then + Call env.reporter_.Log("Cannot access artifact: " & iSource.Text) + Exit Function + End If + + Call EnsureFolderExists(env.fso_.GetParentFolderName(iDestination.Text)) + Call aWrap.SaveAs(iDestination.Text) + Call aWrap.ReleaseDocument(bSaveChanges:=True) + End If + + ExecuteSaveAs = True +End Function + +Private Function ExecuteRun(env As ItemActionEnvironment) As Boolean + ExecuteRun = False + + Dim sExec$: sExec = args_(1) + If VBA.InStr(1, sExec, ".") <> 0 Then _ + sExec = CPath(sExec).ToGlobal(IIf(env.isBuilding_, env.sourceHome_, env.artifactHome_)).Text + + Dim fso As New Scripting.FileSystemObject + Dim sHome$: sHome = fso.GetParentFolderName(sExec) + Dim isPowerShell As Boolean: isPowerShell = VBA.UCase(fso.GetExtensionName(sExec)) = "PS1" + + If isPowerShell Then _ + sExec = "PowerShell -NoProfile -ExecutionPolicy Bypass -Command ""& '" & sExec & "'""" + If args_.Count > 1 Then _ + sExec = sExec & " " & args_(2) + + On Error GoTo EXECUTION_ERROR + Dim iShell As New WshShell + iShell.CurrentDirectory = sHome + ExecuteRun = iShell.Run(sExec, waitOnReturn:=True) = 0 + On Error GoTo 0 + Exit Function + +EXECUTION_ERROR: + Call env.reporter_.Log("Failed to interpret shell command: " & sExec) + On Error GoTo 0 +End Function + +Private Function ExecuteInstall(env As ItemActionEnvironment) As Boolean + Dim iSource As API_Path: Set iSource = CPath(args_(1)).ToGlobal(env.artifactHome_) + Dim sDestination$ + If args_.Count < 2 Then + sDestination = env.installHome_ & "\" & iSource.FileName + Else + sDestination = CPath(args_(2)).ToGlobal(env.installHome_).Text + End If + ExecuteInstall = CopyFileOrFolder(iSource.Text, sDestination, env.fso_) +End Function + +Private Function ExecuteTemplate(env As ItemActionEnvironment) As Boolean + ExecuteTemplate = False + + Dim sSource$: sSource = env.artifactHome_ & "\" & args_(1) + Dim sDestName$ + If args_.Count < 2 Then + sDestName = env.fso_.GetFileName(sSource) + Else + sDestName = args_(2) + End If + + Dim vPath As Variant + For Each vPath In Globals.TemplatePaths + Dim sDestination$: sDestination = CPath(sDestName).ToGlobal(CStr(vPath)).Text + If Not CopyFileOrFolder(sSource, sDestination, env.fso_) Then + Call env.reporter_.Log("Failed to install template: " & sDestination) + Exit Function + End If + Next vPath + + ExecuteTemplate = True +End Function diff --git a/src/builder/InfoBuild.cls b/src/builder/InfoBuild.cls new file mode 100644 index 0000000..ef9ec05 --- /dev/null +++ b/src/builder/InfoBuild.cls @@ -0,0 +1,20 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "InfoBuild" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Public status_ As TBuildStatus +Public date_ As Double +Public tests_ As CDS_InfoTests + +Private Sub Class_Initialize() + status_ = T_BS_PENDING + Set tests_ = New CDS_InfoTests + date_ = Now() +End Sub diff --git a/src/builder/InfoComponent.cls b/src/builder/InfoComponent.cls new file mode 100644 index 0000000..7c95f3a --- /dev/null +++ b/src/builder/InfoComponent.cls @@ -0,0 +1,307 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "InfoComponent" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Public manifest_ As InfoManifest + +Public sourceHome_ As String +Public artifactHome_ As String + +Private version_ As String +Private appWrap_ As Object +Private artSource_ As API_Project +Private bReadOnly_ As Boolean + +Public Function Init(sManifest$, sSources$, sArtifacts$, sVersion$) As Boolean + Set manifest_ = New InfoManifest + sourceHome_ = sSources + artifactHome_ = sArtifacts + version_ = sVersion + Init = manifest_.Init(sManifest) +End Function + +' == Properties = +Public Property Get ItemName() As String + ItemName = manifest_.properties_(MANIFEST_PROPS_NAME) +End Property + +Public Property Get Artifact() As String + Artifact = CPath(manifest_.properties_(MANIFEST_PROPS_ARTIFACT)).ToGlobal(artifactHome_).Text +End Property + +Public Property Get Skeleton() As String + Skeleton = sourceHome_ & "\" & SUBFOLDER_SKELETON & "\" & ItemName +End Property + +' ===== API ===== +Public Function ValidateManifest(bType As TBuildType) As Collection ' of string + If Not OpenArtifact(bReadOnly:=True) Then _ + Exit Function + Set ValidateManifest = manifest_.Validate(bType, appWrap_.Document.VBProject) + Call ReleaseArtifact(bSave:=False) +End Function + +Public Function OpenFileInNewProcess(bReadOnly As Boolean) As Object + Dim iWrap As Object: Set iWrap = AccessArtifact(Artifact, bReadOnly) + If Not iWrap Is Nothing Then _ + Set OpenFileInNewProcess = iWrap.Document +End Function + +Public Function ClearCode() As Boolean + ClearCode = OpenArtifact(bReadOnly:=False) + If ClearCode Then + Call artSource_.RemoveAll + Call ReleaseArtifact(bSave:=True) + End If +End Function + +Public Function ExportCode() As Boolean + ExportCode = OpenArtifact(bReadOnly:=True) + If ExportCode Then + Call artSource_.ExportSrcTo(sourceHome_) + Call ReleaseArtifact(bSave:=False) + ExportCode = manifest_.ExportUI(Artifact, sourceHome_ & "\" & SUBFOLDER_UI) + End If +End Function + +Public Function ExportShared() As Boolean + ExportShared = OpenArtifact(bReadOnly:=True) + If ExportShared Then + Call artSource_.ExportShared + Call ReleaseArtifact(bSave:=False) + End If +End Function + +Public Function ExportAll() As Boolean + ExportAll = OpenArtifact(bReadOnly:=True) + If ExportAll Then + Call artSource_.ExportSrcTo(sourceHome_) + Call artSource_.ExportShared + Call ReleaseArtifact(bSave:=False) + ExportAll = manifest_.ExportUI(Artifact, sourceHome_ & "\" & SUBFOLDER_UI) + End If +End Function + +Public Function ReloadCode() As Boolean + ReloadCode = False + If Not OpenArtifact(bReadOnly:=False) Then _ + Exit Function + Call artSource_.ImportSrcFrom(sourceHome_) + ReloadCode = manifest_.ImportRefs(appWrap_.Document.VBProject) + Call ReleaseArtifact(bSave:=True) + + If ReloadCode Then _ + ReloadCode = manifest_.ImportUI(sourceHome_ & "\" & SUBFOLDER_UI, Artifact) +End Function + +Public Function ReloadShared() As Boolean + ReloadShared = OpenArtifact(bReadOnly:=False) + If ReloadShared Then + Call artSource_.ImportShared + ReloadShared = manifest_.ImportRefs(appWrap_.Document.VBProject) + If ReloadShared Then _ + ReloadShared = CompileVBProject(appWrap_.Document.VBProject) + Call ReleaseArtifact(bSave:=True) + End If +End Function + +Public Function ReloadAll() As Boolean + ReloadAll = OpenArtifact(bReadOnly:=False) + If Not ReloadAll Then _ + Exit Function + + Call artSource_.RemoveAll + Call artSource_.ImportSrcFrom(sourceHome_) + Call artSource_.ImportShared + + ReloadAll = manifest_.ImportRefs(appWrap_.Document.VBProject) + Call ReleaseArtifact(bSave:=True) + + If ReloadAll Then _ + ReloadAll = manifest_.ImportUI(sourceHome_ & "\" & SUBFOLDER_UI, Artifact) +End Function + +Public Function UpdateSkeleton() As Boolean + UpdateSkeleton = manifest_.ExportUI(Artifact, sourceHome_ & "\" & SUBFOLDER_UI) + If Not UpdateSkeleton Then _ + Exit Function + + UpdateSkeleton = OpenArtifact(bReadOnly:=False) + If Not UpdateSkeleton Then _ + Exit Function + + Call artSource_.ExportSrcTo(sourceHome_) + Call artSource_.ExportShared + + Call PrepareSkeletonFor(appWrap_.Document) + Call artSource_.RemoveAll + + Dim fso As New Scripting.FileSystemObject + Dim sSkeletonFile$: sSkeletonFile = Skeleton + Call EnsureFolderExists(fso.GetParentFolderName(sSkeletonFile), fso) + Call appWrap_.SaveAs(sSkeletonFile) + + Call ReleaseArtifact(bSave:=True) +End Function + +Public Function Clear() + Dim fso As New Scripting.FileSystemObject + Dim sFile$: sFile = Artifact + If fso.FileExists(sFile) Then _ + Call fso.DeleteFile(sFile) +End Function + +Public Function Compile() As Boolean + Compile = OpenArtifact(bReadOnly:=True) + If Compile Then + Compile = CompileVBProject(appWrap_.Document.VBProject) + Call ReleaseArtifact(bSave:=False) + End If +End Function + +Public Function Test(ByRef reporter As API_Logger, ByRef iTestOut As IteratorTest) As CDS_InfoTests + If Not OpenArtifact(bReadOnly:=True) Then _ + Exit Function + Set Test = RunTestsInternal(reporter, iTestOut) + Call ReleaseArtifact(bSave:=False) +End Function + +Public Function Build(bType As TBuildType, ByRef reporter As API_Logger, ByRef iTestOut As IteratorTest) As InfoBuild + Set Build = New InfoBuild + Build.status_ = T_BS_FAILED + Call reporter.Log("Start building component: " & ItemName) + Call reporter.Log("Build type: " & BuildDescription(bType)) + + Dim sArtifact$: sArtifact = Artifact + Call reporter.Log("Ensure artifact exists: " & sArtifact) + If Not EnsureArtifactExists() Then _ + Exit Function + + Call reporter.Log("Accessing artifact file: " & sArtifact) + If Not OpenArtifact(bReadOnly:=False) Then _ + Exit Function + + Call reporter.Log("Loading code modules...") + Call artSource_.ImportSrcFrom(sourceHome_) + Call artSource_.ImportShared + Call manifest_.ImportRefs(appWrap_.Document.VBProject) + + If version_ <> vbNullString Then + Call reporter.Log("Writing version number...") + Call UpdateVersionStamp(appWrap_.Document.VBProject, version_) + End If + + Call reporter.Log("Compiling VBProject...") + If Not CompileVBProject(appWrap_.Document.VBProject) Then + Call reporter.Log("Compilation failed") + GoTo SAFE_EXIT + End If + + Call ReleaseArtifact(bSave:=True) + + Call reporter.Log("Importing UI elements...") + If Not manifest_.ImportUI(sourceHome_ & "\" & SUBFOLDER_UI, sArtifact) Then _ + Exit Function + + If Not OpenArtifact(bReadOnly:=True) Then _ + Exit Function + + Call reporter.Log("Validating manifest...") + Dim oErrors As Collection: Set oErrors = manifest_.Validate(bType, appWrap_.Document.VBProject) + If oErrors Is Nothing Then + Call reporter.Log("Validation failed") + GoTo SAFE_EXIT + ElseIf oErrors.Count > 0 Then + Call reporter.Log("Validation failed") + GoTo SAFE_EXIT + End If + + Call reporter.Log("Testing VBProject...") + Dim testResults As CDS_InfoTests: Set testResults = RunTestsInternal(reporter, iTestOut) + Set Build.tests_ = testResults + If testResults Is Nothing Then _ + GoTo SAFE_EXIT + Call reporter.Log("Tests summary: " & testResults.count_ & " | " & testResults.success_ & " / " & testResults.failed_) + If testResults.failed_ <> 0 Then + Call reporter.Log("Tests failed... interrupting build") + GoTo SAFE_EXIT + End If + + If testResults.failed_ <> 0 Then _ + GoTo SAFE_EXIT + + If bType = T_BUILD_RELEASE Then + Call ReleaseArtifact(bSave:=False) + If Not OpenArtifact(bReadOnly:=False) Then _ + Exit Function + Call reporter.Log("Removing debug code for release build...") + Call Dev_RemoveDebugCode(appWrap_.Document.VBProject) + If Not CompileVBProject(appWrap_.Document.VBProject) Then + Call reporter.Log("Compilation failed") + GoTo SAFE_EXIT + End If + End If + + Build.status_ = T_BS_OK +SAFE_EXIT: + Call ReleaseArtifact(bSave:=True) + Call reporter.Log("Finish building component: " & ItemName) +End Function + +' ==== +Private Function EnsureArtifactExists() As Boolean + EnsureArtifactExists = False + + Dim sArtifact$: sArtifact = Artifact + Dim fso As New FileSystemObject + If Not fso.FileExists(sArtifact) Then + Dim sSkeleton$: sSkeleton = Skeleton + If Not fso.FileExists(sSkeleton) Then _ + Exit Function + Call CopyFileOrFolder(sSkeleton, sArtifact, fso) + End If + + EnsureArtifactExists = True +End Function + +Private Function OpenArtifact(bReadOnly As Boolean) As Boolean + Set appWrap_ = AccessArtifact(Artifact, bReadOnly) + If appWrap_ Is Nothing Then + OpenArtifact = False + Exit Function + End If + + Set artSource_ = New API_Project + OpenArtifact = artSource_.Init(appWrap_.Document.VBProject, Globals.SharedHome) + If OpenArtifact Then + bReadOnly_ = bReadOnly + Call manifest_.InitSourceAPI(artSource_) + Else + Call ReleaseArtifact(bSave:=False) + End If +End Function + +Private Function ReleaseArtifact(bSave As Boolean) + If Not artSource_ Is Nothing Then + Call artSource_.Detach + Set artSource_ = Nothing + End If + If Not appWrap_ Is Nothing Then + Call appWrap_.ReleaseDocument(bSaveChanges:=Not bReadOnly_ And bSave) + Set appWrap_ = Nothing + End If +End Function + +Private Function RunTestsInternal(ByRef reporter As API_Logger, ByRef iTestOut As IteratorTest) As CDS_InfoTests + Dim runner As New API_TestRunner + Call runner.Init(appWrap_, reporter, iTestOut) + Set RunTestsInternal = runner.RunAllTests() + Set runner = Nothing +End Function diff --git a/src/builder/InfoGlobals.cls b/src/builder/InfoGlobals.cls new file mode 100644 index 0000000..5926b2f --- /dev/null +++ b/src/builder/InfoGlobals.cls @@ -0,0 +1,86 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "InfoGlobals" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Private Const CELL_SHARED_HOME = "c_SharedHome" +Private Const CELL_CODE_BASE = "c_CodeBase" +Private Const CELL_ARTIFACT_HOME = "c_ArtifactsHome" +Private Const CELL_DEFAULT_REFS = "c_DefaultRefs" +Private Const CELL_LOG_FLAG = "c_LogFlag" +Private Const CELL_LOG_FOLDER = "c_LogLocation" +Private Const CELL_DISTRIBUTION = "c_Distribution" + +Private Const TABLE_GLOBAL_REFS = "t_GlobalRefs" + +Private Const RANGE_TEMPLATES = "r_Templates" + +Public build_ As TBuildType + +Private Sub Class_Initialize() + build_ = T_BUILD_DEBUG +End Sub + +Public Property Get Logger() As API_Logger + Static s_Log As API_Logger + If s_Log Is Nothing Then + Set s_Log = New API_Logger + If ThisWorkbook.Names(CELL_LOG_FLAG).RefersToRange = "True" Then + Dim sName$: sName = GenerateLogFilename(ThisWorkbook.Names(CELL_LOG_FOLDER).RefersToRange) + Call s_Log.Init(sName) + End If + End If + Set Logger = s_Log +End Property + +Public Property Get References() As DB_GlobalRefs + Static s_Refs As DB_GlobalRefs + If s_Refs Is Nothing Then + Set s_Refs = New DB_GlobalRefs + Call s_Refs.Init(ThisWorkbook.Worksheets(SHEET_PARAMETERS).ListObjects(TABLE_GLOBAL_REFS)) + End If + Set References = s_Refs +End Property + +Public Property Get SourceHome() As String + SourceHome = ThisWorkbook.Names(CELL_CODE_BASE).RefersToRange +End Property + +Public Property Get SharedHome() As String + SharedHome = ThisWorkbook.Names(CELL_SHARED_HOME).RefersToRange +End Property + +Public Property Get ArtifactHome() As String + ArtifactHome = ThisWorkbook.Names(CELL_ARTIFACT_HOME).RefersToRange +End Property + +Public Property Get DistributionHome() As String + DistributionHome = ThisWorkbook.Names(CELL_DISTRIBUTION).RefersToRange +End Property + +Public Function TemplatePaths() As Collection + Dim dataRange As Excel.Range: Set dataRange = ThisWorkbook.Names(RANGE_TEMPLATES).RefersToRange + Dim cResult As New Collection + Dim aCell As Excel.Range + For Each aCell In dataRange + Dim sTxt$: sTxt = aCell + If sTxt <> vbNullString Then _ + Call cResult.Add(CPath(sTxt).ToGlobal(ThisWorkbook.Path).Text) + Next aCell + Set TemplatePaths = cResult +End Function + +Public Function DefaultRefs() As String + DefaultRefs = ThisWorkbook.Names(CELL_DEFAULT_REFS).RefersToRange +End Function + +' ========== +Private Function GenerateLogFilename(sFolder$) As String + GenerateLogFilename = sFolder & "\" & Format(Now(), "yyyy-mm-dd") & ".txt" +End Function diff --git a/src/builder/InfoMakefile.cls b/src/builder/InfoMakefile.cls new file mode 100644 index 0000000..e41fce5 --- /dev/null +++ b/src/builder/InfoMakefile.cls @@ -0,0 +1,182 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "InfoMakefile" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Public sPath_ As String + +Public properties_ As Scripting.Dictionary +Public buildActs_ As Collection ' of InfoAction +Public installActs_ As Collection ' of InfoAction + +Public Function Init(sFile$) As Boolean + sPath_ = sFile + + Set properties_ = New Scripting.Dictionary + Set buildActs_ = New Collection + Set installActs_ = New Collection + + Init = LoadFromFile +End Function + +Public Property Get ProductID() As String + ProductID = properties_(MAKEFILE_PROPS_ID) +End Property + +Public Property Get ProductName() As String + ProductName = properties_(MAKEFILE_PROPS_NAME) +End Property + +Public Function Validate(sSourceHome$, sArtifactHome$, bIsBuilding As Boolean) As Collection + On Error GoTo RETURN_NOTHING + + Dim oLog As New Collection + Dim sArtifacts As New Scripting.Dictionary + Set sArtifacts = ValidateBuildActions(sSourceHome, oLog) + Call ValidateInstallActions(sArtifactHome, oLog, sArtifacts, bIsBuilding) + + Set Validate = oLog +RETURN_NOTHING: +End Function + +' ======= +Private Function LoadFromFile() As Boolean + LoadFromFile = False + + Dim fso As New Scripting.FileSystemObject + If Not fso.FileExists(sPath_) Then _ + Exit Function + + Dim adoStream As New ADODB.Stream + adoStream.Charset = "utf-8" + Call adoStream.Open + Call adoStream.LoadFromFile(sPath_) + Dim sLines() As String: sLines = Split(adoStream.ReadText, vbCrLf) + Call adoStream.Close + + Dim nCurrent&: nCurrent = LBound(sLines, 1) + Dim nLast&: nLast = UBound(sLines, 1) + If Not ScanProperties(sLines, nCurrent, nLast) Then _ + Exit Function + If Not ScanActions(sLines, nCurrent, nLast, buildActs_) Then _ + Exit Function + If Not ScanActions(sLines, nCurrent, nLast, installActs_) Then _ + Exit Function + + LoadFromFile = True +End Function + +Private Function ScanProperties(sLines() As String, ByRef nCurrent&, nLast&) As Boolean + ScanProperties = ExtractProperties(sLines, nCurrent, properties_) + If Not ScanProperties Then _ + Exit Function + ScanProperties = _ + properties_.Exists(MAKEFILE_PROPS_ID) _ + And properties_.Exists(MAKEFILE_PROPS_NAME) _ + And properties_.Exists(MAKEFILE_PROPS_ARTIFACT) _ + And properties_.Exists(MAKEFILE_PROPS_SOURCE) _ + And properties_.Exists(MAKEFILE_PROPS_INSTALL) _ + And nCurrent <= nLast +End Function + +Private Function ScanActions(sLines() As String, ByRef nCurrent&, nLast&, ByRef oSink As Collection) As Boolean + ScanActions = False + + Dim iAction As InfoAction + Do While nCurrent <= nLast + Dim sLine$: sLine = sLines(nCurrent) + nCurrent = nCurrent + 1 + If sLine Like "%%*" Then _ + Exit Do + If Left(sLine, 1) = "#" Then _ + GoTo NEXT_LINE + + + Dim nSpace&: nSpace = InStr(1, sLine, " ") + If nSpace = 0 Then _ + GoTo NEXT_LINE + + Set iAction = New InfoAction + + iAction.type_ = Text2ActionType(Trim(Left(sLine, nSpace))) + sLine = Trim(Right(sLine, Len(sLine) - nSpace)) + + Dim nSeparator&: nSeparator = InStr(1, sLine, IIf(iAction.type_ = T_ACT_RUN, "<-", "->")) + If nSeparator = 0 Then + Call iAction.args_.Add(Trim(sLine)) + Else + Call iAction.args_.Add(Trim(Left(sLine, nSeparator - 1))) + Call iAction.args_.Add(Trim(Right(sLine, Len(sLine) - nSeparator - 2))) + End If + Call oSink.Add(iAction) + +NEXT_LINE: + Loop + + ScanActions = True +End Function + +Private Function ValidateBuildActions(sSourceHome$, oLog As Collection) As Scripting.Dictionary + Dim sArtifacts As New Scripting.Dictionary + + Dim anAction As InfoAction + For Each anAction In buildActs_ + Dim arg1$: arg1 = anAction.args_(1) + Dim iPath As API_Path: Set iPath = CPath(arg1).ToGlobal(sSourceHome) + If anAction.type_ <> T_ACT_SAVE_AS Then + If anAction.type_ <> T_ACT_RUN Then _ + If Not iPath.GlobalExists() Then _ + Call oLog.Add("Missing source: " & arg1) + Else + If Not sArtifacts.Exists(arg1) Then _ + Call oLog.Add("Unknown artifact: " & arg1) + End If + + If anAction.IsInstallOnly Or anAction.type_ = T_ACT_UNDEF Then _ + Call oLog.Add("Invalid build action type: " & anAction.Description) + + If anAction.type_ = T_ACT_BUILD Then + Dim iManifest As New InfoManifest + If iManifest.Init(iPath.Text) Then + Call sArtifacts.Add(iManifest.properties_(MANIFEST_PROPS_ARTIFACT), "") + End If + End If + + If anAction.args_.Count > 1 Then _ + Call sArtifacts.Add(anAction.args_(2), "") + If anAction.args_.Count = 1 And anAction.type_ = T_ACT_COPY Then + If iPath.Extension = vbNullString Then + Call sArtifacts.Add(iPath.BaseName, "") + Else + Call sArtifacts.Add(iPath.FileName, "") + End If + End If + Next anAction + + Set ValidateBuildActions = sArtifacts +End Function + +Private Function ValidateInstallActions(sArtifactHome$, oLog As Collection, sArtifacts As Scripting.Dictionary, bIsBuilding As Boolean) + Dim anAction As InfoAction + For Each anAction In installActs_ + If anAction.IsBuildOnly Or anAction.type_ = T_ACT_UNDEF Then _ + Call oLog.Add("Invalid install action type: " & anAction.Description) + + Dim arg1$: arg1 = anAction.args_(1) + Dim iPath As API_Path: Set iPath = CPath(arg1).ToGlobal(sArtifactHome) + If bIsBuilding Then + If Not sArtifacts.Exists(arg1) Then _ + Call oLog.Add("Unknown artifact: " & arg1) + Else + If Not iPath.GlobalExists Then _ + Call oLog.Add("Missing artifact: " & iPath.Text) + End If + Next anAction +End Function + diff --git a/src/builder/InfoManifest.cls b/src/builder/InfoManifest.cls new file mode 100644 index 0000000..ba9256c --- /dev/null +++ b/src/builder/InfoManifest.cls @@ -0,0 +1,413 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "InfoManifest" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Public sPath_ As String + +Public properties_ As Scripting.Dictionary +Public contents_ As Scripting.Dictionary +Public sharedContents_ As Scripting.Dictionary +Public uiElements_ As Collection ' of DescriptorUI +Public references_ As Collection ' of ItemVBReference + +Private fso_ As Scripting.FileSystemObject + +Private Const IMPORTS_MAX_DEPTH = 9 + +Public Function Init(sManifestPath$) As Boolean + sPath_ = sManifestPath$ + + Set properties_ = New Scripting.Dictionary + + Set contents_ = New Scripting.Dictionary + Set sharedContents_ = New Scripting.Dictionary + + Set uiElements_ = New Collection + Set references_ = New Collection + + Set fso_ = New Scripting.FileSystemObject + + Init = LoadFromFile +End Function + +Public Function InitSourceAPI(ByRef target As API_Project) + Call target.SetInternals(contents_, sharedContents_) +End Function + +Public Function AddSrcItem(sName$, sFolder$) + Call contents_.Add(sName, sFolder) +End Function + +Public Function AddSharedItem(sName$, sFolder$) + Call sharedContents_.Add(sName, sFolder) +End Function + +Public Function ExportUI(sSourceFile$, sDestinationFolder$) As Boolean + ExportUI = False + + Dim sZip$: sZip = sDestinationFolder & "\" & "uiTMP" + If Not UnzipFile(sSourceFile, sZip) Then _ + Exit Function + + Dim itemInfo As DescriptorUI + Dim sSourceUI$ + Dim sDestUI$ + For Each itemInfo In uiElements_ + sSourceUI = sZip & "\" & itemInfo.internalPath_ + sDestUI = sDestinationFolder & "\" & itemInfo.sourcePath_ + Call EnsureFolderExists(fso_.GetParentFolderName(sDestUI)) + Call fso_.CopyFile(sSourceUI, sDestUI) + Next itemInfo + + Call fso_.DeleteFolder(sZip) + + ExportUI = True +End Function + +Public Function ImportUI(sSourceFolder$, sDestinationFile$) As Boolean + ImportUI = False + + Dim sZip$: sZip = sSourceFolder & "\" & "uiTMP" + If Not UnzipFile(sDestinationFile, sZip) Then _ + Exit Function + + Dim itemInfo As DescriptorUI + Dim sSourceUI$ + Dim sDestUI$ + For Each itemInfo In uiElements_ + sDestUI = sZip & "\" & itemInfo.internalPath_ + sSourceUI = sSourceFolder & "\" & itemInfo.sourcePath_ + Call EnsureFolderExists(fso_.GetParentFolderName(sDestUI)) + Call fso_.CopyFile(sSourceUI, sDestUI) + Next itemInfo + + ImportUI = ZipFolder(sZip, sDestinationFile) + Call fso_.DeleteFolder(sZip) +End Function + +Public Function ImportRefs(target As VBIDE.VBProject) As Boolean + ImportRefs = False + + Dim iReferences As DB_GlobalRefs: Set iReferences = Globals.References + Dim iRef As ItemVBReference + For Each iRef In references_ + Select Case iRef.type_ + Case T_REF_GLOBAL_NAME + Dim theRef As ItemVBReference: Set theRef = iReferences.GetGlobal(iRef.id_) + If theRef Is Nothing Then + Debug.Print "Invalid global name " & iRef.id_ + Exit Function + End If + If Not TryImportRefGUID(target, theRef.id_, theRef.major_, theRef.minor_) Then _ + Exit Function + Case T_REF_GUID + If Not TryImportRefGUID(target, iRef.id_) Then _ + Exit Function + Case T_REF_FILE + If Not TryImportRefFile(iRef.id_, target) Then _ + Exit Function + End Select + Next iRef + + ImportRefs = True +End Function + +Public Function Validate(bType As TBuildType, target As VBIDE.VBProject) As Collection + Dim oLog As New Collection + + Call ValidateCode(bType, target, Globals.SharedHome, oLog) + Call ValidateRefs(target, Globals.References, oLog) + + Set Validate = oLog +End Function + +' ======= +Private Function LoadFromFile() As Boolean + LoadFromFile = False + + If Not fso_.FileExists(sPath_) Then _ + Exit Function + + Dim adoStream As New ADODB.Stream + adoStream.Charset = "utf-8" + Call adoStream.Open + Call adoStream.LoadFromFile(sPath_) + Dim sLines() As String: sLines = Split(adoStream.ReadText, vbCrLf) + Call adoStream.Close + + Dim nCurrent&: nCurrent = LBound(sLines, 1) + Dim nLast&: nLast = UBound(sLines, 1) + If Not ScanProperties(sLines, nCurrent, nLast) Then _ + Exit Function + If Not ScanStructure(sLines, nCurrent, nLast, "AddSharedItem") Then _ + Exit Function + If Not ScanStructure(sLines, nCurrent, nLast, "AddSrcItem") Then _ + Exit Function + If Not ScanSubstitutes(sLines, nCurrent, nLast) Then _ + Exit Function + If Not ScanReferences(sLines, nCurrent, nLast) Then _ + Exit Function + + LoadFromFile = True +End Function + +Private Function ScanProperties(sLines() As String, ByRef nCurrent&, nLast&) As Boolean + ScanProperties = ExtractProperties(sLines, nCurrent, properties_) + If Not ScanProperties Then _ + Exit Function + ScanProperties = properties_.Exists(MANIFEST_PROPS_NAME) _ + And properties_.Exists(MANIFEST_PROPS_ARTIFACT) _ + And nCurrent <= nLast +End Function + +Private Function ScanStructure(sLines() As String, ByRef nCurrent&, nLast&, sCallback$) As Boolean + ScanStructure = False + + Dim sParts(0 To IMPORTS_MAX_DEPTH) As String + Dim lastLvl&: lastLvl = -1 + Do While nCurrent <= nLast + Dim sLine$: sLine = sLines(nCurrent) + nCurrent = nCurrent + 1 + If sLine Like "%%*" Then _ + Exit Do + + Dim sPart$: sPart = Replace(sLine, vbTab, "") + If sPart = vbNullString Then _ + GoTo NEXT_LINE + If Left(sPart, 1) = "#" Then _ + GoTo NEXT_LINE + + Dim nLevel&: nLevel = Len(sLine) - Len(sPart) + sPart = Trim(sPart) + If nLevel > lastLvl Then _ + GoTo NEXT_PART + + Dim sFile$: sFile = sParts(lastLvl) + If Not IsVBAModuleExtension(fso_.GetExtensionName(sFile)) Then _ + GoTo NEXT_PART + + Call CallByName(Me, sCallback, VbMethod, sFile, ConstructPath(sParts, 0, lastLvl - 1)) + +NEXT_PART: + sParts(nLevel) = sPart + lastLvl = nLevel + +NEXT_LINE: + Loop + If lastLvl <> -1 Then _ + If IsVBAModuleExtension(fso_.GetExtensionName(sParts(lastLvl))) Then _ + Call CallByName(Me, sCallback, VbMethod, sParts(lastLvl), ConstructPath(sParts, 0, lastLvl - 1)) + ScanStructure = nCurrent <= nLast +End Function + +Private Function ScanSubstitutes(sLines() As String, ByRef nCurrent&, nLast&) As Boolean + Dim oNewUI As DescriptorUI + Do While nCurrent <= nLast + Dim sLine$: sLine = sLines(nCurrent) + nCurrent = nCurrent + 1 + If sLine Like "%%*" Then _ + Exit Do + + Dim nSeparator&: nSeparator = InStr(1, sLine, "->") + If nSeparator = 0 Then _ + GoTo NEXT_LINE + If Left(sLine, 1) = "#" Then _ + GoTo NEXT_LINE + + Set oNewUI = New DescriptorUI + oNewUI.sourcePath_ = Trim(Left(sLine, nSeparator - 1)) + oNewUI.internalPath_ = Trim(Right(sLine, Len(sLine) - nSeparator - 1)) + Call uiElements_.Add(oNewUI) + +NEXT_LINE: + Loop + ScanSubstitutes = nCurrent <= nLast +End Function + +Private Function ScanReferences(sLines() As String, ByRef nCurrent&, nLast&) As Boolean + ScanReferences = False + + Dim oRef As ItemVBReference + Do While nCurrent <= nLast + Dim sLine$: sLine = sLines(nCurrent) + nCurrent = nCurrent + 1 + + Dim nSeparator&: nSeparator = InStr(1, sLine, ":") + If nSeparator = 0 Then _ + GoTo NEXT_LINE + If Left(sLine, 1) = "#" Then _ + GoTo NEXT_LINE + + Set oRef = New ItemVBReference + oRef.type_ = RefTypeFrom(Trim(Left(sLine, nSeparator - 1))) + If oRef.type_ = T_REF_UNDEF Then _ + Exit Function + oRef.id_ = Trim(Right(sLine, Len(sLine) - nSeparator)) + + Call references_.Add(oRef) + +NEXT_LINE: + Loop + + ScanReferences = True +End Function + +Private Function ConstructPath(sParts() As String, nFirst&, nLast&) As String + Dim nItem& + For nItem = nFirst To nLast Step 1 + ConstructPath = ConstructPath & sParts(nItem) & "\" + Next nItem + ConstructPath = Left(ConstructPath, Len(ConstructPath) - 1) +End Function + +Private Function ComponentName(target As VBComponent) As String + ComponentName = target.Name + Select Case target.Type + Case vbext_ct_ClassModule: ComponentName = ComponentName & ".cls" + Case vbext_ct_MSForm: ComponentName = ComponentName & ".frm" + Case vbext_ct_StdModule: ComponentName = ComponentName & ".bas" + Case Else: ComponentName = vbNullString + End Select +End Function + +Private Function ValidateCode(bType As TBuildType, target As VBIDE.VBProject, sSharedHome$, ByRef oLog As Collection) + Dim oNames As New Collection + Dim aComp As VBIDE.VBComponent + For Each aComp In target.VBComponents + If aComp.Type = vbext_ct_Document Then _ + GoTo NEXT_COMP + + Dim sName$: sName = ComponentName(aComp) + Call oNames.Add("", sName) + If contents_.Exists(sName) Then _ + GoTo NEXT_COMP + If sharedContents_.Exists(sName) Then _ + GoTo NEXT_COMP + + Call oLog.Add("Unlisted module: " & sName) + +NEXT_COMP: + Next aComp + + Dim aName As Variant + For Each aName In contents_ + If bType = T_BUILD_RELEASE Then _ + If Dev_IsTestingModule(CStr(aName)) Then _ + GoTo NEXT_NAME + If Not InCollection(CStr(aName), oNames) Then _ + Call oLog.Add("Missing module: " & aName) +NEXT_NAME: + Next aName + + For Each aName In sharedContents_ + If Not InCollection(CStr(aName), oNames) Then _ + Call oLog.Add("Missing shared module: " & aName) + Dim sItemPath$: sItemPath = sSharedHome & "\" & sharedContents_(aName) & "\" & aName + If Not fso_.FileExists(sItemPath) Then _ + Call oLog.Add("Missing shared file: " & sItemPath) + Next aName +End Function + +Private Function ValidateRefs(target As VBIDE.VBProject, Globals As DB_GlobalRefs, ByRef oLog As Collection) + Dim projRef As VBIDE.Reference + For Each projRef In target.References + If Not ValidateProjectRef(projRef, target, Globals) Then _ + Call oLog.Add("Unlisted reference: " & projRef.Name) + Next projRef + + Dim iRef As ItemVBReference + For Each iRef In references_ + If Not ValidateManifestRef(target, iRef, Globals) Then _ + Call oLog.Add("Missing reference: " & iRef.ToString) + Next iRef +End Function + +Private Function ValidateProjectRef(target As VBIDE.Reference, proj As VBIDE.VBProject, Globals As DB_GlobalRefs) As Boolean + If target.Type = vbext_rk_Project Then + ValidateProjectRef = True + Exit Function + End If + If IsBasicReference(target, proj) Then + ValidateProjectRef = True + Exit Function + End If + Dim iRef As ItemVBReference + For Each iRef In references_ + If iRef.Test(target) Then + ValidateProjectRef = True + Exit Function + End If + Next iRef + ValidateProjectRef = False +End Function + +Private Function ValidateManifestRef(proj As VBIDE.VBProject, target As ItemVBReference, Globals As DB_GlobalRefs) As Boolean + Dim aRef As VBIDE.Reference + For Each aRef In proj.References + If target.Test(aRef) Then + ValidateManifestRef = True + Exit Function + End If + Next aRef + ValidateManifestRef = False +End Function + +Private Function TryImportRefGUID(target As VBIDE.VBProject, sID$, Optional nMajor& = 0, Optional nMinor& = 0) As Boolean + Dim aRef As VBIDE.Reference + For Each aRef In target.References + If aRef.GUID = sID Then + TryImportRefGUID = True + Exit Function + End If + Next aRef + + On Error GoTo PARSE_ERRORS + Dim newRef As VBIDE.Reference: Set newRef = target.References.AddFromGuid(sID, nMajor, nMinor) + On Error GoTo 0 + + TryImportRefGUID = Not newRef.IsBroken + + Exit Function +PARSE_ERRORS: + TryImportRefGUID = ProcessImportError(Err.Number, Err.Description, sID) +End Function + +Private Function TryImportRefFile(sFile$, target As VBIDE.VBProject) As Boolean + Dim aRef As VBIDE.Reference + For Each aRef In target.References + If aRef.FullPath = sFile Then + TryImportRefFile = True + Exit Function + End If + Next aRef + + On Error GoTo PARSE_ERRORS + Dim newRef As VBIDE.Reference: Set newRef = target.References.AddFromFile(sFile) + On Error GoTo 0 + + TryImportRefFile = newRef.IsBroken + + Exit Function +PARSE_ERRORS: + TryImportRefFile = ProcessImportError(Err.Number, Err.Description, sFile) +End Function + +Private Function ProcessImportError(nID&, sDescription$, sData$) As Boolean + ProcessImportError = False + Select Case Err.Number + Case 48: Debug.Print "Failed loading " & sData + Case 1004: Debug.Print "Trust center is not cofigured properly" + Case -2147319779: Debug.Print "Unknown GUID " & sData + Case 32813 + Debug.Print "Already loaded " & sData + ProcessImportError = True + Case Else: Debug.Print "Unknown error " & nID & " " & sDescription + End Select +End Function diff --git a/src/builder/InfoProduct.cls b/src/builder/InfoProduct.cls new file mode 100644 index 0000000..ca9ded0 --- /dev/null +++ b/src/builder/InfoProduct.cls @@ -0,0 +1,151 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "InfoProduct" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Public make_ As InfoMakefile +Public version_ As String +Public distribute_ As Boolean + +Public Function Init(sMakefile$, sVersion$, doDistribute As Boolean) As Boolean + Set make_ = New InfoMakefile + Init = make_.Init(sMakefile) + distribute_ = doDistribute + version_ = sVersion +End Function + +' == Properties = +Public Property Get ProductID() As String + ProductID = make_.properties_(MAKEFILE_PROPS_ID) +End Property + +Public Property Get Name() As String + Name = make_.properties_(MAKEFILE_PROPS_NAME) +End Property + +Public Property Get SourceHome() As String + SourceHome = CPath(make_.properties_(MAKEFILE_PROPS_SOURCE)).ToGlobal(Globals.SourceHome).Text +End Property + +Public Property Get ArtifactHome() As String + ArtifactHome = CPath(make_.properties_(MAKEFILE_PROPS_ARTIFACT)).ToGlobal(Globals.ArtifactHome).Text +End Property + +Public Property Get InstallHome() As String + InstallHome = make_.properties_(MAKEFILE_PROPS_INSTALL) +End Property + +' ===== Actions ====== +Public Function ValidateMakefile() As Collection ' of string + Set ValidateMakefile = make_.Validate(SourceHome, ArtifactHome, bIsBuilding:=True) +End Function + +Public Function Build(bType As TBuildType, reporter As API_Logger, dbComponents As DB_Components, testDB As DB_Tests, Optional sArtPath$ = vbNullString) As Boolean + Build = False + + If sArtPath = vbNullString Then _ + sArtPath = ArtifactHome + + Dim env As New ItemActionEnvironment + Call env.InitBuild(bType, Name, version_, sArtPath, SourceHome, reporter, dbComponents, testDB) + + Call reporter.Log("Start building product: " & Name) + Call reporter.Log("Version: " & version_) + + If version_ <> vbNullString Then + Call reporter.Log("Updating version file: " & version_) + If Not VersionCreateFile(env.sourceHome_, version_) Then _ + Exit Function + End If + + Call reporter.Log("Validating makefile...") + If Not ActValidateMakefile(env) Then _ + Exit Function + + Call reporter.Log("Processing build actions...") + If Not ActProcessActions(env) Then _ + Exit Function + + Build = True + Call reporter.Log("Finish building product: " & Name) +End Function + +Public Function Install(bType As TBuildType, reporter As API_Logger, dbComponents As DB_Components, sInstallPath$) As Boolean + Install = False + + Call reporter.Log("Start installing product: " & Name) + Call reporter.Log("Version: " & version_) + + Dim env As New ItemActionEnvironment + Call env.InitInstall(bType, Name, version_, ArtifactHome, SourceHome, reporter, dbComponents, sInstallPath) + + Call reporter.Log("Validating makefile...") + If Not ActValidateMakefile(env) Then _ + Exit Function + + Call reporter.Log("Processing installation actions...") + If Not ActProcessActions(env) Then _ + Exit Function + + If distribute_ Then + Call reporter.Log("Updating distribution manifest...") + If Not UpdateDistribution Then + Call reporter.Log("Failed to save distribution manifest...") + Exit Function + End If + End If + + Install = True + Call reporter.Log("Finish installing product: " & Name) +End Function + +' ============= +Private Function UpdateDistribution() As Boolean + Dim sManifest$: sManifest = Globals.DistributionHome & "\" & FILE_DISTRIBUTION_MANIFEST + Dim iDistribution As New API_DistrManifest + Call iDistribution.LoadFrom(sManifest) + Call iDistribution.SetVersion(make_.ProductID, version_) + UpdateDistribution = iDistribution.SaveTo(sManifest) +End Function + +Private Function ActValidateMakefile(env As ItemActionEnvironment) As Boolean + ActValidateMakefile = False + + Dim oErrors As Collection: Set oErrors = make_.Validate(env.sourceHome_, env.artifactHome_, env.isBuilding_) + If oErrors Is Nothing Then + Call env.reporter_.Log("Validation failed") + Exit Function + ElseIf oErrors.Count > 0 Then + Call env.reporter_.Log("Validation failed") + Exit Function + End If + + ActValidateMakefile = True +End Function + +Private Function ActProcessActions(env As ItemActionEnvironment) As Boolean + ActProcessActions = False + + Dim nAct&: nAct = 1 + Dim anAction As InfoAction + Dim acts As Collection: Set acts = IIf(env.isBuilding_, make_.buildActs_, make_.installActs_) + For Each anAction In acts + Call env.reporter_.Log("A" & nAct & "/" & acts.Count & ": " & anAction.Description) + On Error GoTo FAILED_ACTION + If Not anAction.Execute(env) Then +FAILED_ACTION: + Call env.reporter_.Log("Action failed: " & nAct) + Exit Function + End If + On Error GoTo 0 + nAct = nAct + 1 + Next anAction + + ActProcessActions = True +End Function diff --git a/src/builder/ItemActionEnvironment.cls b/src/builder/ItemActionEnvironment.cls new file mode 100644 index 0000000..7db4ed7 --- /dev/null +++ b/src/builder/ItemActionEnvironment.cls @@ -0,0 +1,66 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "ItemActionEnvironment" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Public reporter_ As API_Logger +Public isBuilding_ As Boolean +Public buildType_ As TBuildType + +Public product_ As String +Public productVersion_ As String +Public artifactHome_ As String +Public sourceHome_ As String +Public components_ As DB_Components + +Public installHome_ As String +Public testDB_ As DB_Tests + +Public fso_ As Scripting.FileSystemObject + +Public Function InitBuild(bType As TBuildType, sProduct$, sVersion$, sArtifacts$, sSources$, _ + aReporter As API_Logger, _ + dbComponents As DB_Components, _ + testDB As DB_Tests) + isBuilding_ = True + buildType_ = bType + Set fso_ = New Scripting.FileSystemObject + + product_ = sProduct + productVersion_ = sVersion + artifactHome_ = sArtifacts + sourceHome_ = sSources + + Set reporter_ = aReporter + Set components_ = dbComponents + Set testDB_ = testDB +End Function + +Public Function InitInstall(bType As TBuildType, sProduct$, sVersion$, sArtifacts$, sSources$, _ + aReporter As API_Logger, _ + dbComponents As DB_Components, sInstall$) + isBuilding_ = False + buildType_ = bType + Set fso_ = New Scripting.FileSystemObject + + product_ = sProduct + productVersion_ = sVersion + artifactHome_ = sArtifacts + sourceHome_ = sSources + + Set reporter_ = aReporter + Set components_ = dbComponents + + installHome_ = sInstall + + artifactHome_ = sArtifacts + sourceHome_ = sSources +End Function + + diff --git a/src/builder/ItemVBReference.cls b/src/builder/ItemVBReference.cls new file mode 100644 index 0000000..714a2f2 --- /dev/null +++ b/src/builder/ItemVBReference.cls @@ -0,0 +1,35 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "ItemVBReference" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Public type_ As TVBReference +Public id_ As String +Public major_ As Long +Public minor_ As Long + +Public Function Init(aType As TVBReference, sData$, nMajor&, nMinor&) + type_ = aType + id_ = sData + major_ = nMajor + minor_ = nMinor +End Function + +Public Function Test(target As VBIDE.Reference) As Boolean + Select Case type_ + Case T_REF_GLOBAL_NAME: Test = target.Name = id_ + Case T_REF_GUID: Test = target.GUID = id_ + Case T_REF_FILE: Test = target.FullPath = id_ + Case Else: Test = False + End Select +End Function + +Public Function ToString() As String + ToString = RefTypeToString(type_) & " : " & id_ +End Function diff --git a/src/builder/Main.bas b/src/builder/Main.bas new file mode 100644 index 0000000..f08fef7 --- /dev/null +++ b/src/builder/Main.bas @@ -0,0 +1,671 @@ +Attribute VB_Name = "Main" +Option Explicit + +Public Sub RunEditVBAMake() + Dim iProduct As IteratorProduct: Set iProduct = GetSelectedProduct + If iProduct Is Nothing Then _ + Exit Sub + + Dim fso As New Scripting.FileSystemObject + Dim sMake$: sMake = iProduct.Makefile + If Not fso.FileExists(sMake) Then + Call UserInteraction.ShowMessage(EM_MISSING_MAKEFILE, sMake) + Exit Sub + End If + + Dim oShell As New Shell32.Shell + Call oShell.Open(iProduct.Makefile) +End Sub + +Public Sub RunEditManifect() + Dim iComp As IteratorComponent: Set iComp = GetSelectedComponent + If iComp Is Nothing Then _ + Exit Sub + + Dim fso As New Scripting.FileSystemObject + Dim sManifest$: sManifest = iComp.ManifestFile + If Not fso.FileExists(sManifest) Then + Call UserInteraction.ShowMessage(EM_MISSING_MANIFEST, sManifest) + Exit Sub + End If + + Dim oShell As New Shell32.Shell + Call oShell.Open(sManifest) +End Sub + +Public Sub RunOpenSrc() + Dim iModel As InfoProduct: Set iModel = GetProductModel(GetSelectedProduct) + If iModel Is Nothing Then _ + Exit Sub + + Dim oShell As New Shell32.Shell + Call oShell.Open(iModel.SourceHome) +End Sub + +Public Sub RunOpenArtifact() + Dim iModel As InfoProduct: Set iModel = GetProductModel(GetSelectedProduct) + If iModel Is Nothing Then _ + Exit Sub + + Dim oShell As New Shell32.Shell + Call oShell.Open(iModel.ArtifactHome) +End Sub + +Public Sub RunTestVBAMake() + Dim iModel As InfoProduct: Set iModel = GetProductModel(GetSelectedProduct) + If iModel Is Nothing Then _ + Exit Sub + + Dim oErrors As Collection: Set oErrors = iModel.ValidateMakefile() + If oErrors Is Nothing Then + Call UserInteraction.ShowMessage(IM_MAKEFILE_FAILED, "") + ElseIf oErrors.Count = 0 Then + Call UserInteraction.ShowMessage(IM_MAKEFILE_OK) + Else + Call UserInteraction.ShowMessage(IM_MAKEFILE_FAILED, CollectionToLines(oErrors)) + End If +End Sub + +Public Sub RunBuildProduct() + Dim theTimer As New API_Timer: Call theTimer.Start + Dim iProduct As IteratorProduct: Set iProduct = GetSelectedProduct + Dim iModel As InfoProduct: Set iModel = GetProductModel(iProduct) + If iModel Is Nothing Then _ + Exit Sub + + Call ClearTests + If BuildProduct(Globals.build_, iProduct, iModel, Globals.Logger) Then + Call UserInteraction.ShowMessage(IM_BUILD_OK, theTimer.TimeStr) + Else + Call UserInteraction.ShowMessage(IM_BUILD_FAILED) + End If +End Sub + +Public Sub RunInstallProduct() + Dim theTimer As New API_Timer: Call theTimer.Start + Dim iProduct As IteratorProduct: Set iProduct = GetSelectedProduct + Dim iModel As InfoProduct: Set iModel = GetProductModel(iProduct) + If iModel Is Nothing Then _ + Exit Sub + + If InstallProduct(Globals.build_, iProduct, iModel, Globals.Logger) Then + Call UserInteraction.ShowMessage(IM_INSTALL_OK, theTimer.TimeStr) + Else + Call UserInteraction.ShowMessage(IM_INSTALL_FAILED) + End If +End Sub + +Public Sub RunAddProduct() + Dim sFolder$: sFolder = UserInteraction.PromptFolder(Globals.SourceHome & "\") + If sFolder = vbNullString Then _ + Exit Sub + + Dim sFile$: sFile = sFolder & "\" & MAKEFILE_NAME + Dim fso As New Scripting.FileSystemObject + If Not fso.FileExists(sFile) Then + Call UserInteraction.ShowMessage(EM_MAKEFILE_MISSING, sFile) + Exit Sub + End If + + Dim iMake As New InfoMakefile + If Not iMake.Init(sFolder & "\" & MAKEFILE_NAME) Then + Call UserInteraction.ShowMessage(EM_MAKEFILE_LOADING_FAILED) + Exit Sub + End If + + Dim dbProducts As DB_Products: Set dbProducts = AccessProducts + If Not dbProducts.Insert(iMake) Then + Call UserInteraction.ShowMessage(EM_PRODUCT_ALREADY_EXISTS, iMake.ProductName) + Else + Call UserInteraction.ShowMessage(IM_PRODUCT_ADDED, iMake.ProductName) + End If +End Sub + +Public Sub RunArchiveVersion() + Dim iProduct As IteratorProduct: Set iProduct = GetSelectedProduct + Dim iModel As InfoProduct: Set iModel = GetProductModel(iProduct) + If iModel Is Nothing Then _ + Exit Sub + + Dim sArchive$: sArchive = ArchiveNameFor(iModel, Now) + sArchive = CreateVersionArchive(Globals.build_, sArchive, iModel, Globals.Logger) + If sArchive = vbNullString Then + Call UserInteraction.ShowMessage(IM_BUILD_FAILED) + Exit Sub + End If + + Dim fso As New Scripting.FileSystemObject + Dim oShell As New Shell32.Shell + Call oShell.Open(fso.GetParentFolderName(sArchive)) +End Sub + +Public Sub RunTestManifest() + Dim iComp As IteratorComponent: Set iComp = GetSelectedComponent + Dim iModel As InfoComponent: Set iModel = GetComponentModel(iComp) + If iModel Is Nothing Then _ + Exit Sub + + Dim oErrors As Collection: Set oErrors = iModel.ValidateManifest(Globals.build_) + If oErrors Is Nothing Then + Call iComp.ResetBuildStatus + Call UserInteraction.ShowMessage(IM_MANIFEST_FAILED, "") + ElseIf oErrors.Count = 0 Then + Call UserInteraction.ShowMessage(IM_MANIFEST_OK) + Else + Call iComp.ResetBuildStatus + Call UserInteraction.ShowMessage(IM_MANIFEST_FAILED, CollectionToLines(oErrors)) + End If +End Sub + +Public Sub RunOpenComponent() + Dim iComp As IteratorComponent: Set iComp = GetSelectedComponent + Dim iModel As InfoComponent: Set iModel = GetComponentModel(iComp) + If iModel Is Nothing Then _ + Exit Sub + + Dim iDoc As Object: Set iDoc = iModel.OpenFileInNewProcess(bReadOnly:=False) + If Not iDoc Is Nothing Then _ + If Not TypeOf iDoc Is Visio.Document Then _ + Call iDoc.Activate +End Sub + +Public Sub RunOpenComponentReadOnly() + Dim iComp As IteratorComponent: Set iComp = GetSelectedComponent + Dim iModel As InfoComponent: Set iModel = GetComponentModel(iComp) + If iModel Is Nothing Then _ + Exit Sub + + Dim iDoc As Object: Set iDoc = iModel.OpenFileInNewProcess(bReadOnly:=True) + If Not iDoc Is Nothing Then _ + Call iDoc.Activate +End Sub + +Public Sub RunBuildComponent() + Dim theTimer As New API_Timer: Call theTimer.Start + Dim iComp As IteratorComponent: Set iComp = GetSelectedComponent + Dim iModel As InfoComponent: Set iModel = GetComponentModel(iComp) + If iModel Is Nothing Then _ + Exit Sub + If iModel.ItemName = ThisWorkbook.Name Then + Call UserInteraction.ShowMessage(EM_SELF_MODIFICATION) + Exit Sub + End If + + Call ClearTests + + Dim buildRes As InfoBuild + Set buildRes = iModel.Build(Globals.build_, Globals.Logger, AccessTests.OutputFor(iComp.Product, iComp.ItemName)) + Call iComp.SyncBuildInfo(buildRes) + If buildRes.status_ = T_BS_OK Then + Call UserInteraction.ShowMessage(IM_BUILD_OK, theTimer.TimeStr) + Else + Call UserInteraction.ShowMessage(IM_BUILD_FAILED) + End If +End Sub + +Public Sub RunTestComponent() + Dim iComp As IteratorComponent: Set iComp = GetSelectedComponent + Dim iModel As InfoComponent: Set iModel = GetComponentModel(iComp) + If iModel Is Nothing Then _ + Exit Sub + If iModel.ItemName = ThisWorkbook.Name Then + Call UserInteraction.ShowMessage(EM_SELF_MODIFICATION) + Exit Sub + End If + + Call ClearTests + + Dim theTimer As New API_Timer: Call theTimer.Start + + Dim iOutput As IteratorTest: Set iOutput = AccessTests.OutputFor(iComp.Product, iComp.ItemName) + Dim testRes As CDS_InfoTests: Set testRes = iModel.Test(Globals.Logger, iOutput) + If testRes Is Nothing Then + Call UserInteraction.ShowMessage(EM_TESTS_FAILED) + Exit Sub + End If + Call iComp.SyncTestInfo(testRes) + Call iOutput.RemoveRow + + If testRes.failed_ <> 0 Then _ + iComp.BuildStatus = T_BS_FAILED + If testRes.count_ = 0 Then + Call UserInteraction.ShowMessage(IM_TESTS_NOT_FOUND) + Else + Call UserInteraction.ShowMessage(IM_TESTS_COMPLETE, testRes.count_, testRes.success_, testRes.failed_, theTimer.TimeStr) + End If +End Sub + +Public Sub RunExportCode() + Dim iComp As IteratorComponent: Set iComp = GetSelectedComponent + Dim iModel As InfoComponent: Set iModel = GetComponentModel(iComp) + If iModel Is Nothing Then _ + Exit Sub + + Call iComp.ResetBuildStatus + If Not iModel.ExportCode() Then _ + Call UserInteraction.ShowMessage(EM_EXPORT_FAILED) +End Sub + +Public Sub RunExportShared() + Dim iComp As IteratorComponent: Set iComp = GetSelectedComponent + Dim iModel As InfoComponent: Set iModel = GetComponentModel(iComp) + If iModel Is Nothing Then _ + Exit Sub + + Call iComp.ResetBuildStatus + If Not iModel.ExportShared() Then _ + Call UserInteraction.ShowMessage(EM_EXPORT_FAILED) +End Sub + +Public Sub RunReloadCode() + Dim iComp As IteratorComponent: Set iComp = GetSelectedComponent + Dim iModel As InfoComponent: Set iModel = GetComponentModel(iComp) + If iModel Is Nothing Then _ + Exit Sub + If iModel.ItemName = ThisWorkbook.Name Then + Call UserInteraction.ShowMessage(EM_SELF_MODIFICATION) + Exit Sub + End If + + Call iComp.ResetBuildStatus + If Not iModel.ReloadCode() Then + Call iComp.SetBuildStatus(T_BS_FAILED) + Call UserInteraction.ShowMessage(EM_IMPORT_FAILED) + End If +End Sub + +Public Sub RunReloadShared() + Dim iComp As IteratorComponent: Set iComp = GetSelectedComponent + Dim iModel As InfoComponent: Set iModel = GetComponentModel(iComp) + If iModel Is Nothing Then _ + Exit Sub + If iModel.ItemName = ThisWorkbook.Name Then + Call UserInteraction.ShowMessage(EM_SELF_MODIFICATION) + Exit Sub + End If + + Call iComp.ResetBuildStatus + If Not iModel.ReloadShared() Then + Call iComp.SetBuildStatus(T_BS_FAILED) + Call UserInteraction.ShowMessage(EM_IMPORT_FAILED) + End If +End Sub + +Public Sub RunUpdateTarget() + Dim iModel As InfoComponent: Set iModel = GetComponentModel(GetSelectedComponent) + If iModel Is Nothing Then _ + Exit Sub + + Dim sFile$: sFile = UserInteraction.PromptFile(ThisWorkbook.Path & "\") + If sFile = vbNullString Then _ + Exit Sub + + Dim fso As New Scripting.FileSystemObject + If Not fso.FileExists(sFile) Then + Call UserInteraction.ShowMessage(EM_MISSING_FILE, sFile) + Exit Sub + End If + + If UpdateCodeIn(iModel, sFile) Then + Call UserInteraction.ShowMessage(IM_UPDATE_OK) + Else + Call UserInteraction.ShowMessage(IM_UPDATE_FAILED) + End If +End Sub + +Public Sub RunTargetClearCode() + Dim sFile$: sFile = UserInteraction.PromptFile(ThisWorkbook.Path & "\") + If sFile = vbNullString Then _ + Exit Sub + + Dim fso As New Scripting.FileSystemObject + If Not fso.FileExists(sFile) Then + Call UserInteraction.ShowMessage(EM_MISSING_FILE, sFile) + Exit Sub + End If + + If Not UserInteraction.AskQuestion(QM_CODE_DELETE_CONFIRM) Then _ + Exit Sub + + Dim wrap As Object: Set wrap = AccessArtifact(sFile, bReadOnly:=False) + If wrap Is Nothing Then _ + Exit Sub + + Dim proj As New API_Project: Call proj.Init(wrap.Document.VBProject, "") + Call proj.RemoveAll + + Call wrap.ReleaseDocument(bSaveChanges:=True) +End Sub + +Public Sub RunTargetUpdateShared() + Dim sFile$: sFile = UserInteraction.PromptFile(ThisWorkbook.Path & "\") + If sFile = vbNullString Then _ + Exit Sub + + Dim fso As New Scripting.FileSystemObject + If Not fso.FileExists(sFile) Then + Call UserInteraction.ShowMessage(EM_MISSING_FILE, sFile) + Exit Sub + End If + + Dim wrap As Object: Set wrap = AccessArtifact(sFile, bReadOnly:=False) + If wrap Is Nothing Then _ + Exit Sub + + Dim iUpdated As Collection: Set iUpdated = UpdateSharedIn(Globals.SharedHome, wrap.Document.VBProject) + Call wrap.ReleaseDocument(bSaveChanges:=True) + + If iUpdated Is Nothing Then + Call UserInteraction.ShowMessage(IM_UPDATE_SHARED_FAILED, iUpdated.Count) + Else + Call UserInteraction.ShowMessage(IM_UPDATE_SHARED_OK, iUpdated.Count, CollectionToLines(iUpdated)) + End If +End Sub + +Public Sub RunOpenLog() + Dim oShell As New Shell32.Shell + Call oShell.Open(Globals.Logger.LogFileName) +End Sub + +Public Sub RunOpenConfig() + Dim oShell As New Shell32.Shell + Call oShell.Open(VBA.Environ$("USERPROFILE") & "\.concept\concept-options.json") +End Sub + +Public Sub RunOpenConcept() + Dim oShell As New Shell32.Shell + Call oShell.Open(VBA.Environ$("USERPROFILE") & "\.concept") +End Sub + +Public Sub RunOpenVBCommons() + Dim oShell As New Shell32.Shell + Call oShell.Open(Globals.SharedHome) +End Sub + +Public Sub RunOpenAppData() + Dim oShell As New Shell32.Shell + Call oShell.Open(VBA.Environ$("APPDATA")) +End Sub + +Public Sub RunOpenUserProfile() + Dim oShell As New Shell32.Shell + Call oShell.Open(VBA.Environ$("USERPROFILE")) +End Sub + +Public Sub RunOpenCIHT() + Dim oShell As New Shell32.Shell + Call oShell.Open(SERVER_PATH_CIHT) +End Sub + +Public Sub RunOpenInstall() + Dim oShell As New Shell32.Shell + Call oShell.Open(SERVER_PATH_AUTOMATION) +End Sub + +Public Sub RunListRefs() + Dim iData As IteratorComponent: Set iData = GetSelectedComponent + If iData Is Nothing Then _ + Exit Sub + + Dim appWrap As Object: Set appWrap = AccessArtifact(iData.ArtifactFile, bReadOnly:=True) + If appWrap Is Nothing Then _ + Exit Sub + + Dim xlOut As New API_XLWrapper: Call xlOut.SetApplication(ThisWorkbook.Application) + Call ScanRefsFrom(appWrap.Document.VBProject, xlOut.NewDocument.Worksheets(1)) + Call appWrap.ReleaseDocument + Call xlOut.Document.Activate +End Sub + +Public Sub RunListModules() + Dim iData As IteratorComponent: Set iData = GetSelectedComponent + If iData Is Nothing Then _ + Exit Sub + + Dim appWrap As Object: Set appWrap = AccessArtifact(iData.ArtifactFile, bReadOnly:=True) + If appWrap Is Nothing Then _ + Exit Sub + + Dim xlOut As New API_XLWrapper: Call xlOut.SetApplication(ThisWorkbook.Application) + Call ScanModules(appWrap.Document.VBProject, xlOut.NewDocument.Worksheets(1)) + Call appWrap.ReleaseDocument + Call xlOut.Document.Activate +End Sub + +Public Sub RunListShared() + Call ClearShared + + Dim dbShared As DB_SharedModules: Set dbShared = AccessShared + Dim iOut As IteratorSharedModule: Set iOut = dbShared.INew + + Dim oErrors As Collection: Set oErrors = ScanShared(iOut, Globals.SharedHome, AccessComponents) + Call ThisWorkbook.Sheets(SHEET_SHARED).Activate + + If oErrors.Count <> 0 Then _ + Call UserInteraction.ShowMessage(IM_SHARED_INVALID, CollectionToLines(oErrors)) +End Sub + +Public Sub RunReloadAllShared() + Dim iData As IteratorComponent: Set iData = AccessComponents.IBegin + Dim oErrors As New Collection + Do While Not iData.IsDone + Dim iModel As InfoComponent: Set iModel = iData.Model + If iModel Is Nothing Then + Call oErrors.Add(iData.ItemName) + ElseIf Not iModel.ReloadShared Then + Call iData.SetBuildStatus(T_BS_FAILED) + Call oErrors.Add(iData.ItemName) + End If + Call iData.ResetBuildStatus + Call iData.Increment + Loop + + If oErrors.Count = 0 Then + Call UserInteraction.ShowMessage(IM_RELOAD_OK) + Else + Call UserInteraction.ShowMessage(IM_RELOAD_FAILED, CollectionToLines(oErrors)) + End If +End Sub + +Public Sub RunCompileAll() + Dim iData As IteratorComponent: Set iData = AccessComponents.IBegin + Dim oErrors As New Collection + Do While Not iData.IsDone + Dim iModel As InfoComponent: Set iModel = iData.Model + If iModel Is Nothing Then + Call oErrors.Add(iData.ItemName) + ElseIf Not iModel.Compile Then + Call oErrors.Add(iData.ItemName) + End If + Call iData.Increment + Loop + + If oErrors.Count = 0 Then + Call UserInteraction.ShowMessage(IM_COMPILATION_OK) + Else + Call UserInteraction.ShowMessage(IM_COMPILATION_FAILED, CollectionToLines(oErrors)) + End If +End Sub + +Public Sub RunAllTests() + Call ClearTests + + Dim theTimer As New API_Timer: Call theTimer.Start + Dim reporter As API_Logger: Set reporter = Globals.Logger + Dim totalRes As New CDS_InfoTests + + Dim iData As IteratorComponent: Set iData = AccessComponents.IBegin + Do While Not iData.IsDone + Dim iModel As InfoComponent: Set iModel = iData.Model + If iModel Is Nothing Then _ + GoTo NEXT_COMPONENT + + Dim iOutput As IteratorTest: Set iOutput = AccessTests.OutputFor(iData.Product, iData.ItemName) + Dim localRes As CDS_InfoTests: Set localRes = iModel.Test(reporter, iOutput) + If localRes Is Nothing Then + iData.BuildStatus = T_BS_FAILED + GoTo NEXT_COMPONENT + End If + If localRes.failed_ <> 0 Then _ + iData.BuildStatus = T_BS_FAILED + + Call iData.SyncTestInfo(localRes) + Call totalRes.MergeStats(localRes) + Call iOutput.RemoveRow + +NEXT_COMPONENT: + Call iData.Increment + Loop + + Call UserInteraction.ShowMessage(IM_TESTS_COMPLETE, totalRes.count_, totalRes.success_, totalRes.failed_, theTimer.TimeStr) +End Sub + +Public Sub RunBuildAll() + Call ClearTests + + Dim theTimer As New API_Timer: Call theTimer.Start + Dim bType As TBuildType: bType = Globals.build_ + Dim reporter As API_Logger: Set reporter = Globals.Logger + Dim iProduct As IteratorProduct: Set iProduct = AccessProducts.IBegin + Dim nCount&: nCount = 0 + Dim nSuccess&: nSuccess = 0 + Do While Not iProduct.IsDone + nCount = nCount + 1 + Dim iModel As InfoProduct: Set iModel = iProduct.Model(AccessComponents) + If iModel Is Nothing Then + Call iProduct.SetBuildStatus(T_BS_FAILED) + GoTo NEXT_PRODUCT + End If + + If BuildProduct(bType, iProduct, iModel, reporter) Then _ + nSuccess = nSuccess + 1 + +NEXT_PRODUCT: + Call iProduct.Increment + Loop + + Call UserInteraction.ShowMessage(IM_BATCH_BUILD, nSuccess, nCount, theTimer.TimeStr) +End Sub + +Public Sub RunInstallAll() + Call ClearTests + + Dim theTimer As New API_Timer: Call theTimer.Start + Dim bType As TBuildType: bType = Globals.build_ + Dim reporter As API_Logger: Set reporter = Globals.Logger + Dim iProduct As IteratorProduct: Set iProduct = AccessProducts.IBegin + Dim nCount&: nCount = 0 + Dim nSuccess&: nSuccess = 0 + Do While Not iProduct.IsDone + nCount = nCount + 1 + Dim iModel As InfoProduct: Set iModel = iProduct.Model(AccessComponents) + If iModel Is Nothing Then + Call iProduct.SetBuildStatus(T_BS_FAILED) + GoTo NEXT_PRODUCT + End If + + If InstallProduct(bType, iProduct, iModel, reporter) Then _ + nSuccess = nSuccess + 1 + +NEXT_PRODUCT: + Call iProduct.Increment + Loop + + Call UserInteraction.ShowMessage(IM_BATCH_INSTALL, nSuccess, nCount, theTimer.TimeStr) +End Sub + +Public Sub RunUpdateSkeleton() + Dim iModel As InfoComponent: Set iModel = GetComponentModel(GetSelectedComponent) + If iModel Is Nothing Then _ + Exit Sub + + If Not iModel.UpdateSkeleton() Then _ + Call UserInteraction.ShowMessage(EM_SKELETON_FAILED) +End Sub + +Public Sub RunScanFolder() + Dim sFolder$: sFolder = UserInteraction.PromptFolder(Globals.SourceHome & "\") + If sFolder = vbNullString Then _ + Exit Sub + + Dim nCount&: nCount = ScanProducts(sFolder) + Call UserInteraction.ShowMessage(IM_SCAN_OK, nCount) +End Sub + +' ====== +Private Function GetSelectedComponent() As IteratorComponent + Dim sel As Excel.Range: Set sel = Excel.Selection + If ThisWorkbook.ActiveSheet.Name <> SHEET_COMPONENTS _ + Or sel.Parent.Parent.Name <> ThisWorkbook.Name _ + Or sel.Rows.Count <> 1 Then + Call UserInteraction.ShowMessage(EM_INVALID_SELECTION) + Exit Function + End If + + Dim nRow&: nRow = sel.Cells(1, 1).Row + If nRow < FIRST_ROW Then + Call UserInteraction.ShowMessage(EM_INVALID_SELECTION) + Exit Function + End If + + Dim res As New IteratorComponent: Call res.Init(ThisWorkbook.Sheets(SHEET_COMPONENTS), nRow) + If res.IsDone() Then + Call UserInteraction.ShowMessage(EM_INVALID_SELECTION) + Exit Function + End If + + Set GetSelectedComponent = res +End Function + +Private Function GetComponentModel(target As IteratorComponent) As InfoComponent + If target Is Nothing Then _ + Exit Function + Dim iModel As InfoComponent: Set iModel = target.Model + If iModel Is Nothing Then + Call UserInteraction.ShowMessage(EM_INVALID_COMPONENT) + Exit Function + End If + Set GetComponentModel = iModel +End Function + +Private Function GetSelectedProduct() As IteratorProduct + Dim sel As Excel.Range: Set sel = Excel.Selection + If sel.Parent.Parent.Name <> ThisWorkbook.Name _ + Or sel.Rows.Count <> 1 Then + Call UserInteraction.ShowMessage(EM_INVALID_SELECTION) + Exit Function + End If + + Dim nRow&: nRow = sel.Cells(1, 1).Row + If nRow < FIRST_ROW Then + Call UserInteraction.ShowMessage(EM_INVALID_SELECTION) + Exit Function + End If + + Dim iTable As Excel.ListObject: Set iTable = sel.ListObject + If iTable Is Nothing Then _ + GoTo MISSING_PRODUCT + Dim iCell As Excel.Range: Set iCell = iTable.HeaderRowRange.Cells.Find("ProductName") + If iCell Is Nothing Then _ + GoTo MISSING_PRODUCT + + Dim sName$: sName = sel.Parent.Cells(nRow, iCell.Column) + Dim projects As New DB_Products: Set projects = AccessProducts + If projects.Contains(sName) Then + Set GetSelectedProduct = projects.Access(sName) + Else +MISSING_PRODUCT: + Call UserInteraction.ShowMessage(EM_MISSING_PRODUCT, sName) + End If +End Function + +Private Function GetProductModel(target As IteratorProduct) As InfoProduct + If target Is Nothing Then _ + Exit Function + Dim iModel As InfoProduct: Set iModel = target.Model(AccessComponents) + If iModel Is Nothing Then + Call UserInteraction.ShowMessage(EM_INVALID_PRODUCT) + Exit Function + End If + Set GetProductModel = iModel +End Function diff --git a/src/builder/MainImpl.bas b/src/builder/MainImpl.bas new file mode 100644 index 0000000..27c2fd0 --- /dev/null +++ b/src/builder/MainImpl.bas @@ -0,0 +1,234 @@ +Attribute VB_Name = "MainImpl" +Option Private Module +Option Explicit + +Public Function ClearTests() + Call ThisWorkbook.Sheets(SHEET_TESTS).UsedRange.Offset(1, 0).ClearContents +End Function + +Public Function ClearShared() + Call ThisWorkbook.Sheets(SHEET_SHARED).UsedRange.Offset(1, 0).ClearContents +End Function + +Public Function CPath(sPath$) As API_Path + Set CPath = New API_Path + Call CPath.FromString(sPath) +End Function + +Public Function ScanRefsFrom(target As VBIDE.VBProject, iOut As Excel.Worksheet) + iOut.Cells(1, S_R_NAME) = "Name" + iOut.Cells(1, S_R_TYPE) = "Type" + iOut.Cells(1, S_R_FILE) = "File" + iOut.Cells(1, S_R_GUID) = "GUID" + iOut.Cells(1, S_R_BROKEN) = "IsBroken" + iOut.Cells(1, S_R_MAJOR) = "Major" + iOut.Cells(1, S_R_MINOR) = "Minor" + iOut.Cells(1, S_R_DESCRIPTION) = "Description" + + Dim rowOut&: rowOut = 2 + Dim aRef As VBIDE.Reference + For Each aRef In target.References + iOut.Cells(rowOut, S_R_NAME) = aRef.Name + iOut.Cells(rowOut, S_R_TYPE) = aRef.Type + iOut.Cells(rowOut, S_R_FILE) = aRef.FullPath + iOut.Cells(rowOut, S_R_GUID) = aRef.GUID + iOut.Cells(rowOut, S_R_BROKEN) = IIf(aRef.IsBroken, 1, 0) + iOut.Cells(rowOut, S_R_MAJOR) = aRef.Major + iOut.Cells(rowOut, S_R_MINOR) = aRef.Minor + iOut.Cells(rowOut, S_R_DESCRIPTION) = aRef.Description + rowOut = rowOut + 1 + Next aRef +End Function + +Public Function ScanModules(target As VBIDE.VBProject, iOut As Excel.Worksheet) + Dim fso As New Scripting.FileSystemObject + + iOut.Cells(1, S_M_NAME) = "Name" + iOut.Cells(1, S_M_TYPE) = "Type" + iOut.Cells(1, S_M_SHARED) = "Shared" + iOut.Cells(1, S_M_LINES_TOTAL) = "LoC" + iOut.Cells(1, S_M_LINES_DECLARATIONS) = "LoD" + iOut.Cells(1, S_M_COUNT_API) = "API" + + Dim rowOut&: rowOut = 2 + Dim aComp As VBIDE.VBComponent + For Each aComp In target.VBComponents + If aComp.CodeModule.CountOfLines >= LOC_MINIMUM Then + iOut.Cells(rowOut, S_M_NAME) = aComp.Name + iOut.Cells(rowOut, S_M_TYPE) = aComp.Type + iOut.Cells(rowOut, S_M_SHARED) = GetSharedPrefix(aComp) + iOut.Cells(rowOut, S_M_LINES_TOTAL) = aComp.CodeModule.CountOfLines + iOut.Cells(rowOut, S_M_LINES_DECLARATIONS) = aComp.CodeModule.CountOfDeclarationLines + iOut.Cells(rowOut, S_M_COUNT_API) = Dev_CountPublicAPI(aComp.CodeModule) + rowOut = rowOut + 1 + End If + Next aComp +End Function + +Public Function ScanShared(iOut As IteratorSharedModule, sSource$, dbComponents As DB_Components) As Collection + Dim fso As New Scripting.FileSystemObject + If Not fso.FolderExists(sSource) Then _ + Exit Function + + Dim xlWrap As New API_XLWrapper: Call xlWrap.SetApplication(ThisWorkbook.Application) + If xlWrap.NewDocument() Is Nothing Then _ + Exit Function + + Dim oInserter As New CB_AddModule: Call oInserter.Init(iOut, xlWrap.Document.VBProject, fso.GetFolder(sSource).Path) + Call ForEachFileRecursive(sSource, oInserter, "TryAddModule") + Call xlWrap.ReleaseDocument + + Set ScanShared = UpdateSharedUsage(iOut, dbComponents) +End Function + +Public Function UpdateSharedIn(sShared$, target As VBIDE.VBProject) As Collection + Dim iShared As Scripting.Dictionary: Set iShared = GetAllSharedModules(sShared) + Dim iUpdated As New Collection + + Dim iProject As New API_Project: Call iProject.Init(target) + + Dim aComponent As VBComponent + For Each aComponent In target.VBComponents + Dim sName$: sName = aComponent.Name + If iShared.Exists(sName) Then _ + If iProject.ReloadFrom(iShared(sName)) Then _ + Call iUpdated.Add(sName) + Next aComponent + + If CompileVBProject(target) Then _ + Set UpdateSharedIn = iUpdated +End Function + +Public Function BuildProduct(bType As TBuildType, iProduct As IteratorProduct, iModel As InfoProduct, reporter As API_Logger) As Boolean + BuildProduct = iModel.Build(bType, reporter, AccessComponents, AccessTests) + Call iProduct.SetBuildStatus(IIf(BuildProduct, T_BS_OK, T_BS_FAILED)) +End Function + +Public Function InstallProduct(bType As TBuildType, iProduct As IteratorProduct, iModel As InfoProduct, reporter As API_Logger) As Boolean + If iProduct.Status <> T_BS_OK Then + Call reporter.Log("The project wasnt built... Rebuilding") + If Not BuildProduct(bType, iProduct, iModel, reporter) Then + InstallProduct = False + Exit Function + End If + End If + + InstallProduct = iModel.Install(bType, reporter, AccessComponents, iModel.InstallHome) + If InstallProduct Then _ + iProduct.InstallDate = Now +End Function + +Public Function UpdateCodeIn(iModel As InfoComponent, sTargetFile$) As Boolean + Call iModel.manifest_.properties_.Remove(MANIFEST_PROPS_ARTIFACT) + Call iModel.manifest_.properties_.Add(MANIFEST_PROPS_ARTIFACT, sTargetFile) + UpdateCodeIn = iModel.ReloadAll +End Function + +Public Function IsBasicReference(target As VBIDE.Reference, proj As VBIDE.VBProject) As Boolean + If VBA.InStr(1, Globals.DefaultRefs, target.Name) <> 0 Then + IsBasicReference = True + Exit Function + End If + + Dim fso As New FileSystemObject + Dim tAppl As TApplication: tAppl = ApplicationFromExtension(fso.GetExtensionName(proj.FileName)) + Select Case tAppl + Case T_APP_WORD: IsBasicReference = target.Name = "Word" + Case T_APP_EXCEL: IsBasicReference = target.Name = "Excel" + Case T_APP_VISIO: IsBasicReference = target.Name = "Visio" + Case Else: IsBasicReference = False + End Select +End Function + +Public Function CreateVersionArchive(bType As TBuildType, sArchiveFolder$, target As InfoProduct, theLog As API_Logger) As String + Call ClearTests + + Call theLog.Log("Preparing version files... Rebuilding project") + If Not target.Build(bType, theLog, AccessComponents, AccessTests, sArchiveFolder) Then _ + GoTo SAFE_EXIT + + Call theLog.Log("Creating archive") + Dim sArchiveFile$: sArchiveFile = sArchiveFolder & ".zip" + If ZipFolder(sArchiveFolder, sArchiveFile) Then _ + CreateVersionArchive = sArchiveFile + +SAFE_EXIT: + Dim fso As New Scripting.FileSystemObject + If fso.FolderExists(sArchiveFolder) Then _ + Call fso.DeleteFolder(sArchiveFolder) +End Function + +Public Function ArchiveNameFor(prod As InfoProduct, DDate As Double) As String + ArchiveNameFor = _ + prod.ArtifactHome & "\" & _ + SUBFOLDER_VERSION_ARCHIVE & "\" & _ + Format(DDate, "yyyymmdd") & " " & _ + prod.Name +End Function + +Public Function ScanProducts(sTarget$) As Long + Dim iCallback As New CB_AddProduct: Call iCallback.Init(AccessProducts) + Call ForEachFolderRecursive(sTarget, iCallback, "ScanFolder") + ScanProducts = iCallback.newCount_ +End Function + +' ===== +Private Function GetSharedPrefix(target As VBIDE.VBComponent) As String + Dim iShared As IteratorSharedModule: Set iShared = AccessShared.FindName(target.Name) + If Not iShared Is Nothing Then _ + GetSharedPrefix = iShared.Path +End Function + +Private Function GetAllSharedModules(sShared$) As Scripting.Dictionary + Dim iShared As New CB_SharedModules: Call iShared.Init + Call ForEachFileRecursive(sShared, iShared, "ProcessFile") + Set GetAllSharedModules = iShared.data_ +End Function + +Private Function UpdateSharedUsage(ByRef iOut As IteratorSharedModule, dbComponents As DB_Components) As Collection + Dim fso As New Scripting.FileSystemObject + Dim iComp As IteratorComponent: Set iComp = dbComponents.IBegin + Dim iData As New Scripting.Dictionary + Do While Not iComp.IsDone + Dim iManifest As InfoManifest: Set iManifest = iComp.ScanManifest + If iManifest Is Nothing Then _ + GoTo NEXT_COMPONENT + + Dim sComponent$: sComponent = iComp.ItemName + Dim vModule As Variant + For Each vModule In iManifest.sharedContents_ + Dim sModuleName$: sModuleName = fso.GetBaseName(CStr(vModule)) + If Not iData.Exists(sModuleName) Then _ + Call iData.Add(sModuleName, New Collection) + Call iData(sModuleName).Add(sComponent) + Next vModule +NEXT_COMPONENT: + Call iComp.Increment + Loop + + Call iOut.GoFirst + Do While Not iOut.IsDone + Dim sName$: sName = iOut.Name + If Not iData.Exists(sName) Then _ + GoTo NEXT_ROW + + Dim sItem As Variant + Dim sComps$: sComps = vbNullString + For Each sItem In iData(sName) + If sComps <> vbNullString Then _ + sComps = sComps & ", " + sComps = sComps & CStr(sItem) + Next sItem + iOut.UsedIn = sComps + Call iData.Remove(sName) +NEXT_ROW: + Call iOut.Increment + Loop + + Dim oErrors As New Collection + Dim aKey As Variant + For Each aKey In iData + Call oErrors.Add(CStr(aKey)) + Next aKey + Set UpdateSharedUsage = oErrors +End Function diff --git a/src/builder/database/DB_Components.cls b/src/builder/database/DB_Components.cls new file mode 100644 index 0000000..da14861 --- /dev/null +++ b/src/builder/database/DB_Components.cls @@ -0,0 +1,51 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "DB_Components" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Private data_ As Excel.Worksheet + +Public Function Init(target As Excel.Worksheet) + Set data_ = target +End Function + +Public Function IBegin() As IteratorComponent + Set IBegin = New IteratorComponent + Call IBegin.Init(data_) +End Function + +Public Function ILast() As IteratorComponent + Set ILast = New IteratorComponent + Call ILast.Init(data_) + Call ILast.GoLast +End Function + +Public Function INew() As IteratorComponent + Set INew = New IteratorComponent + Call INew.Init(data_) + Call INew.GoLast + Call INew.Increment +End Function + +Public Function Contains(sProduct$, sComponent$) As Boolean + Contains = Not Access(sProduct, sComponent) Is Nothing +End Function + +Public Function Access(sProduct$, sComponent$) As IteratorComponent + Dim iComp As New IteratorComponent: Call iComp.Init(data_) + Do While Not iComp.IsDone + If iComp.Product = sProduct Then + If iComp.ItemName = sComponent Then + Set Access = iComp + Exit Function + End If + End If + Call iComp.Increment + Loop +End Function diff --git a/src/builder/database/DB_GlobalRefs.cls b/src/builder/database/DB_GlobalRefs.cls new file mode 100644 index 0000000..38629f4 --- /dev/null +++ b/src/builder/database/DB_GlobalRefs.cls @@ -0,0 +1,31 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "DB_GlobalRefs" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Private data_ As Excel.ListObject + +Public Function Init(target As Excel.ListObject) + Set data_ = target +End Function + +Public Function GetGlobal(sGlobalName$) As ItemVBReference + Dim dataRng As Excel.Range: Set dataRng = data_.Range + Dim nRow&: nRow = FIRST_ROW + For nRow = FIRST_ROW To dataRng.Rows.Count Step 1 + If dataRng.Cells(nRow, S_G_NAME_ID) = sGlobalName Then + Set GetGlobal = New ItemVBReference + Call GetGlobal.Init(T_REF_GUID, _ + dataRng.Cells(nRow, S_G_GUID), _ + dataRng.Cells(nRow, S_G_MAJOR), _ + dataRng.Cells(nRow, S_G_MINOR)) + Exit Function + End If + Next nRow +End Function diff --git a/src/builder/database/DB_Products.cls b/src/builder/database/DB_Products.cls new file mode 100644 index 0000000..6d03c4e --- /dev/null +++ b/src/builder/database/DB_Products.cls @@ -0,0 +1,61 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "DB_Products" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Private data_ As Excel.Worksheet +Private components_ As DB_Components + +Public Function Init(target As Excel.Worksheet, iComponents As DB_Components) + Set components_ = iComponents + Set data_ = target +End Function + +Public Function IBegin() As IteratorProduct + Set IBegin = New IteratorProduct + Call IBegin.Init(data_) +End Function + +Public Function ILast() As IteratorProduct + Set ILast = New IteratorProduct + Call ILast.Init(data_) + Call ILast.GoLast +End Function + +Public Function INew() As IteratorProduct + Set INew = New IteratorProduct + Call INew.Init(data_) + Call INew.GoLast + Call INew.Increment +End Function + +Public Function Contains(sProduct$) As Boolean + Contains = Not Access(sProduct) Is Nothing +End Function + +Public Function Access(sProduct$) As IteratorProduct + Dim iProd As New IteratorProduct: Call iProd.Init(data_) + Do While Not iProd.IsDone + If iProd.Name = sProduct Then + Set Access = iProd + Exit Function + End If + Call iProd.Increment + Loop +End Function + +Public Function Insert(iMake As InfoMakefile) As Boolean + Insert = Not Contains(iMake.ProductName) + If Not Insert Then _ + Exit Function + + Dim iNewProduct As IteratorProduct: Set iNewProduct = INew + Call iNewProduct.SyncMakefile(iMake) + Call iNewProduct.SyncManifests(iMake, components_) +End Function diff --git a/src/builder/database/DB_SharedModules.cls b/src/builder/database/DB_SharedModules.cls new file mode 100644 index 0000000..0256a45 --- /dev/null +++ b/src/builder/database/DB_SharedModules.cls @@ -0,0 +1,46 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "DB_SharedModules" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Private data_ As Excel.Worksheet + +Public Function Init(target As Excel.Worksheet) + Set data_ = target +End Function + +Public Function IBegin() As IteratorSharedModule + Set IBegin = New IteratorSharedModule + Call IBegin.Init(data_) +End Function + +Public Function ILast() As IteratorSharedModule + Set ILast = New IteratorSharedModule + Call ILast.Init(data_) + Call ILast.GoLast +End Function + +Public Function INew() As IteratorSharedModule + Set INew = New IteratorSharedModule + Call INew.Init(data_) + Call INew.GoLast + Call INew.Increment +End Function + +Public Function FindName(sTarget$) As IteratorSharedModule + Dim iModule As IteratorSharedModule + Set iModule = IBegin + Do While Not iModule.IsDone + If iModule.Name = sTarget Then + Set FindName = iModule + Exit Function + End If + Call iModule.Increment + Loop +End Function diff --git a/src/builder/database/DB_Tests.cls b/src/builder/database/DB_Tests.cls new file mode 100644 index 0000000..b1fffca --- /dev/null +++ b/src/builder/database/DB_Tests.cls @@ -0,0 +1,41 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "DB_Tests" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Private data_ As Excel.Worksheet + +Public Function Init(target As Excel.Worksheet) + Set data_ = target +End Function + +Public Function IBegin() As IteratorTest + Set IBegin = New IteratorTest + Call IBegin.Init(data_) +End Function + +Public Function ILast() As IteratorTest + Set ILast = New IteratorTest + Call ILast.Init(data_) + Call ILast.GoLast +End Function + +Public Function INew() As IteratorTest + Set INew = New IteratorTest + Call INew.Init(data_) + Call INew.GoLast + Call INew.Increment +End Function + +Public Function OutputFor(sProduct$, sComponent$) As IteratorTest + Dim iRes As IteratorTest: Set iRes = INew + iRes.Product = sProduct + iRes.Component = sComponent + Set OutputFor = iRes +End Function diff --git a/src/builder/database/DataAccess.bas b/src/builder/database/DataAccess.bas new file mode 100644 index 0000000..3e352a7 --- /dev/null +++ b/src/builder/database/DataAccess.bas @@ -0,0 +1,101 @@ +Attribute VB_Name = "DataAccess" +Option Private Module +Option Explicit + +Public Function Globals() As InfoGlobals + Static s_Globals As InfoGlobals + If s_Globals Is Nothing Then _ + Set s_Globals = New InfoGlobals + Set Globals = s_Globals +End Function + +Public Function AccessArtifact(sPath$, bReadOnly As Boolean) As Object + Dim fso As New Scripting.FileSystemObject + Dim tAppl As TApplication: tAppl = ApplicationFromExtension(fso.GetExtensionName(sPath)) + If tAppl = T_APP_UNDEF Then + Debug.Print "Unsupported file extenstion: " & sPath + Exit Function + End If + + Dim appWrap As Object: Set appWrap = GetWrapper(tAppl, sPath, bReadOnly) + If appWrap.Document Is Nothing Then _ + Exit Function + Set AccessArtifact = appWrap +End Function + +Public Function AccessProducts() As DB_Products + Static s_Products As DB_Products + + If s_Products Is Nothing Then + Set s_Products = New DB_Products + Call s_Products.Init(ThisWorkbook.Worksheets(SHEET_PRODUCTS), AccessComponents) + End If + + Set AccessProducts = s_Products +End Function + +Public Function AccessComponents() As DB_Components + Static s_Components As DB_Components + + If s_Components Is Nothing Then + Set s_Components = New DB_Components + Call s_Components.Init(ThisWorkbook.Worksheets(SHEET_COMPONENTS)) + End If + + Set AccessComponents = s_Components +End Function + +Public Function AccessTests() As DB_Tests + Static s_Tests As DB_Tests + + If s_Tests Is Nothing Then + Set s_Tests = New DB_Tests + Call s_Tests.Init(ThisWorkbook.Worksheets(SHEET_TESTS)) + End If + + Set AccessTests = s_Tests +End Function + +Public Function AccessShared() As DB_SharedModules + Static s_Shared As DB_SharedModules + + If s_Shared Is Nothing Then + Set s_Shared = New DB_SharedModules + Call s_Shared.Init(ThisWorkbook.Worksheets(SHEET_SHARED)) + End If + + Set AccessShared = s_Shared +End Function + +Public Function AccessProduct(target$) As IteratorProduct + Dim projects As DB_Products: Set projects = AccessProducts + If Not projects.Contains(target) Then _ + Exit Function + Set AccessProduct = projects.Access(target) +End Function + +' ======= +Private Function GetWrapper(tAppl As TApplication, sPath$, bReadOnly As Boolean) As Object + Select Case tAppl + Case T_APP_WORD + Set GetWrapper = New API_WordWrapper + Call GetWrapper.SetReporter(Globals.Logger) + Call GetWrapper.CreateApplication + Call GetWrapper.OpenDocument(sPath, bReadOnly:=bReadOnly) + + Case T_APP_EXCEL + Set GetWrapper = New API_XLWrapper + Call GetWrapper.SetReporter(Globals.Logger) + Call GetWrapper.CreateApplication + Call GetWrapper.OpenDocument(sPath, bReadOnly:=bReadOnly) + + Case T_APP_VISIO + Set GetWrapper = New API_VsoWrapper + Call GetWrapper.SetReporter(Globals.Logger) + Call GetWrapper.CreateApplication + Dim nFlags As Integer: nFlags = visOpenDontList + visOpenDeclineAutoRefresh + If bReadOnly Then _ + nFlags = nFlags + visOpenRO + Call GetWrapper.OpenDocument(sPath, nFlags) + End Select +End Function diff --git a/src/builder/database/IteratorComponent.cls b/src/builder/database/IteratorComponent.cls new file mode 100644 index 0000000..61abedb --- /dev/null +++ b/src/builder/database/IteratorComponent.cls @@ -0,0 +1,209 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "IteratorComponent" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +' _C_ - project components +Private Enum ComponentStruct + [_First] = 1 + + S_C_PRODUCT = 1 + S_C_ITEM_NAME = 2 + S_C_PATH_ARTIFACT = 3 + S_C_LOCAL_MANIFEST = 4 + S_C_BUILD_STATUS = 5 + S_C_BUILD_DATE = 6 + S_C_TESTS_TOTAL = 7 + S_C_TESTS_FAIL = 8 + S_C_TESTS_OK = 9 + + [_Last] = 9 +End Enum + +Public row_ As Long +Private data_ As Excel.Worksheet + +Public Sub Init(target As Excel.Worksheet, Optional tRow& = FIRST_ROW) + Set data_ = target + row_ = tRow +End Sub + +Public Function Increment(Optional inc& = 1) + If row_ + inc > 0 Then _ + row_ = row_ + inc +End Function + +Public Function GoFirst() + row_ = FIRST_ROW +End Function + +Public Function GoLast() + row_ = data_.Columns(S_C_PRODUCT).Find(vbNullString, LookAt:=xlWhole).Row - 1 +End Function + +Public Function IsDone() As Boolean + IsDone = data_.Cells(row_, S_C_PRODUCT) = vbNullString +End Function + +Public Function RemoveRow() + Call data_.Rows(row_).Delete +End Function + +Public Function Model() As InfoComponent + Dim iProduct As IteratorProduct: Set iProduct = AccessProduct(Product) + If iProduct Is Nothing Then _ + Exit Function + + Dim oRes As New InfoComponent + If Not oRes.Init(ManifestFile, iProduct.SourcePath, iProduct.ArtifactPath, iProduct.Version) Then _ + Exit Function + + Call SyncManifest(Product, oRes.manifest_) + Set Model = oRes +End Function + +Public Function ScanManifest() As InfoManifest + Dim iManifest As New InfoManifest + If iManifest.Init(ManifestFile) Then _ + Set ScanManifest = iManifest +End Function + +Public Function SyncManifest(sProduct$, iSource As InfoManifest) + Dim fso As New Scripting.FileSystemObject + Product = sProduct + ItemName = iSource.properties_(MANIFEST_PROPS_NAME) + Artifact = iSource.properties_(MANIFEST_PROPS_ARTIFACT) + Manifest = fso.GetFileName(iSource.sPath_) + Call UpdateItemLink +End Function + +Public Function SyncBuildInfo(iSource As InfoBuild) + BuildStatus = iSource.status_ + BuildDate = iSource.date_ + TestsTotal = iSource.tests_.count_ + TestsOK = iSource.tests_.success_ + TestsFail = iSource.tests_.failed_ +End Function + +Public Function SyncTestInfo(iSource As CDS_InfoTests) + TestsTotal = iSource.count_ + TestsOK = iSource.success_ + TestsFail = iSource.failed_ +End Function + +Public Function ResetBuildStatus() + If BuildStatus <> T_BS_FAILED Then _ + BuildStatus = T_BS_PENDING +End Function + +Public Function SetBuildStatus(newVal As TBuildStatus) + BuildStatus = newVal + BuildDate = Now +End Function + +Public Function UpdateItemLink() + Call XLUpdateHyperlink(data_.Cells(row_, S_C_ITEM_NAME), ArtifactFile) +End Function + +'===== Propertiy Get ===== +Public Property Get ArtifactFile() As String + Dim iProduct As IteratorProduct: Set iProduct = AccessProduct(Product) + If iProduct Is Nothing Then _ + Exit Property + ArtifactFile = CPath(data_.Cells(row_, S_C_PATH_ARTIFACT)).ToGlobal(iProduct.ArtifactPath).Text +End Property + +Public Property Get ManifestFile() As String + ManifestFile = CPath(Manifest).ToGlobal(SourcePath & "\" & SUBFOLDER_SCRIPT).Text +End Property + +Public Property Get SkeletonFile() As String + SkeletonFile = SourcePath & "\" & SUBFOLDER_SKELETON & "\" & ItemName +End Property + +Public Property Get Product() As String + Product = data_.Cells(row_, S_C_PRODUCT) +End Property + +Public Property Get ItemName() As String + ItemName = data_.Cells(row_, S_C_ITEM_NAME) +End Property + +Public Property Get Artifact() As String + Artifact = data_.Cells(row_, S_C_PATH_ARTIFACT) +End Property + +Public Property Get Manifest() As String + Manifest = data_.Cells(row_, S_C_LOCAL_MANIFEST) +End Property + +Public Property Get BuildStatus() As TBuildStatus + BuildStatus = data_.Cells(row_, S_C_BUILD_STATUS) +End Property + +Public Property Get BuildDate() As Double + BuildDate = data_.Cells(row_, S_C_BUILD_DATE) +End Property + +Public Property Get TestsTotal() As Long + TestsTotal = data_.Cells(row_, S_C_TESTS_TOTAL) +End Property + +Public Property Get TestsFail() As Long + TestsFail = data_.Cells(row_, S_C_TESTS_FAIL) +End Property + +Public Property Get TestsOK() As Long + TestsOK = data_.Cells(row_, S_C_TESTS_OK) +End Property + +' ==== Property Let ==== +Public Property Let Product(newVal$) + data_.Cells(row_, S_C_PRODUCT) = newVal +End Property + +Public Property Let ItemName(newVal$) + data_.Cells(row_, S_C_ITEM_NAME) = newVal +End Property + +Public Property Let Artifact(newVal$) + data_.Cells(row_, S_C_PATH_ARTIFACT) = newVal +End Property + +Public Property Let Manifest(newVal$) + data_.Cells(row_, S_C_LOCAL_MANIFEST) = newVal +End Property + +Public Property Let BuildStatus(newVal As TBuildStatus) + data_.Cells(row_, S_C_BUILD_STATUS) = newVal +End Property + +Public Property Let BuildDate(newVal As Double) + data_.Cells(row_, S_C_BUILD_DATE) = newVal +End Property + +Public Property Let TestsTotal(newVal&) + data_.Cells(row_, S_C_TESTS_TOTAL) = newVal +End Property + +Public Property Let TestsFail(newVal&) + data_.Cells(row_, S_C_TESTS_FAIL) = newVal +End Property + +Public Property Let TestsOK(newVal&) + data_.Cells(row_, S_C_TESTS_OK) = newVal +End Property + +' ======= +Private Property Get SourcePath() As String + Dim iProduct As IteratorProduct: Set iProduct = AccessProduct(Product) + If iProduct Is Nothing Then _ + Exit Property + SourcePath = Globals().SourceHome & "\" & iProduct.SourceHome +End Property diff --git a/src/builder/database/IteratorProduct.cls b/src/builder/database/IteratorProduct.cls new file mode 100644 index 0000000..b7182b5 --- /dev/null +++ b/src/builder/database/IteratorProduct.cls @@ -0,0 +1,220 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "IteratorProduct" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +' _P_ - projects +Private Enum ProductStruct + [_First] = 1 + + S_P_ID = 1 + S_P_VERSION = 2 + S_P_NAME = 3 + S_P_DESCRIPTION = 4 + S_P_ARTIFACT_HOME = 5 + S_P_SOURCE_HOME = 6 + S_P_INSTALL_HOME = 7 + + S_P_STATUS = 8 + S_P_LAST_BUILD = 9 + S_P_DISTRIBUTE = 10 + S_P_LAST_INSTALL = 11 + + [_Last] = 11 +End Enum + +Public row_ As Long + +Private data_ As Excel.Worksheet + +Public Sub Init(target As Excel.Worksheet, Optional tRow& = FIRST_ROW) + Set data_ = target + row_ = tRow +End Sub + +Public Function Increment(Optional inc& = 1) + If row_ + inc > 0 Then _ + row_ = row_ + inc +End Function + +Public Function GoFirst() + row_ = FIRST_ROW +End Function + +Public Function GoLast() + row_ = data_.Columns(S_P_NAME).Find(vbNullString, LookAt:=xlWhole).Row - 1 +End Function + +Public Function IsDone() As Boolean + IsDone = data_.Cells(row_, S_P_NAME) = vbNullString +End Function + +Public Function RemoveRow() + Call data_.Rows(row_).Delete +End Function + +Public Function Model(dbComponents As DB_Components) As InfoProduct + Dim oRes As New InfoProduct + If Not oRes.Init(Makefile, Version, IsDistributed) Then _ + Exit Function + + Call SyncMakefile(oRes.make_) + Call SyncManifests(oRes.make_, dbComponents) + Set Model = oRes +End Function + +Public Function SyncMakefile(iSource As InfoMakefile) + ProductID = iSource.properties_(MAKEFILE_PROPS_ID) + Name = iSource.properties_(MAKEFILE_PROPS_NAME) + ArtifactHome = iSource.properties_(MAKEFILE_PROPS_ARTIFACT) + SourceHome = iSource.properties_(MAKEFILE_PROPS_SOURCE) + InstallHome = iSource.properties_(MAKEFILE_PROPS_INSTALL) + If iSource.properties_.Exists(MAKEFILE_PROPS_DESCRIPTION) Then _ + Description = iSource.properties_(MAKEFILE_PROPS_DESCRIPTION) + Call UpdateHyperlinks +End Function + +Public Function SyncManifests(iSource As InfoMakefile, dbComponents As DB_Components) + Dim anAction As InfoAction + Dim sSource$: sSource = SourcePath + Dim sProduct$: sProduct = iSource.properties_(MAKEFILE_PROPS_NAME) + For Each anAction In iSource.buildActs_ + If Not anAction.type_ = T_ACT_BUILD Then _ + GoTo NEXT_ACTION + + Dim sManifest$: sManifest = CPath(anAction.args_(1)).ToGlobal(sSource).Text + Dim iManifest As New InfoManifest + If Not iManifest.Init(sManifest) Then _ + GoTo NEXT_ACTION + + Dim sComponent$: sComponent = iManifest.properties_(MANIFEST_PROPS_NAME) + Dim iComp As IteratorComponent + If dbComponents.Contains(sProduct, sComponent) Then + Set iComp = dbComponents.Access(sProduct, sComponent) + Else + Set iComp = dbComponents.INew + End If + Call iComp.SyncManifest(sProduct, iManifest) + +NEXT_ACTION: + Next anAction +End Function + +Public Function SetBuildStatus(newVal As TBuildStatus) + Status = newVal + BuildDate = Now +End Function + +Public Function UpdateHyperlinks() + Call XLUpdateHyperlink(data_.Cells(row_, S_P_INSTALL_HOME), InstallHome) + Call XLUpdateHyperlink(data_.Cells(row_, S_P_ARTIFACT_HOME), ArtifactPath) + Call XLUpdateHyperlink(data_.Cells(row_, S_P_SOURCE_HOME), SourcePath) +End Function + +'===== Propertiy Get ===== +Public Property Get Makefile() As String + Makefile = Globals.SourceHome & "\" & SourceHome & "\" & MAKEFILE_NAME +End Property + +Public Property Get SourcePath() As String + SourcePath = CPath(SourceHome).ToGlobal(Globals.SourceHome).Text +End Property + +Public Property Get ArtifactPath() As String + ArtifactPath = CPath(ArtifactHome).ToGlobal(Globals.ArtifactHome).Text +End Property + +Public Property Get ProductID() As String + ProductID = data_.Cells(row_, S_P_ID) +End Property + +Public Property Get Version() As String + Version = data_.Cells(row_, S_P_VERSION) +End Property + +Public Property Get Name() As String + Name = data_.Cells(row_, S_P_NAME) +End Property + +Public Property Get Description() As String + Description = data_.Cells(row_, S_P_DESCRIPTION) +End Property + +Public Property Get ArtifactHome() As String + ArtifactHome = data_.Cells(row_, S_P_ARTIFACT_HOME) +End Property + +Public Property Get SourceHome() As String + SourceHome = data_.Cells(row_, S_P_SOURCE_HOME) +End Property + +Public Property Get InstallHome() As String + InstallHome = data_.Cells(row_, S_P_INSTALL_HOME) +End Property + +Public Property Get Status() As TBuildStatus + Status = data_.Cells(row_, S_P_STATUS) +End Property + +Public Property Get BuildDate() As Double + BuildDate = data_.Cells(row_, S_P_LAST_BUILD) +End Property + +Public Property Get IsDistributed() As Boolean + IsDistributed = data_.Cells(row_, S_P_DISTRIBUTE) = "1" +End Property + +Public Property Get InstallDate() As Double + InstallDate = data_.Cells(row_, S_P_LAST_INSTALL) +End Property + +' ==== Property Let ==== +Public Property Let ProductID(newVal$) + data_.Cells(row_, S_P_ID) = newVal +End Property + +Public Property Let Version(newVal$) + data_.Cells(row_, S_P_VERSION) = newVal +End Property + +Public Property Let Name(newVal$) + data_.Cells(row_, S_P_NAME) = newVal +End Property + +Public Property Let Description(newVal$) + data_.Cells(row_, S_P_DESCRIPTION) = newVal +End Property + +Public Property Let ArtifactHome(newVal$) + data_.Cells(row_, S_P_ARTIFACT_HOME) = newVal +End Property + +Public Property Let SourceHome(newVal$) + data_.Cells(row_, S_P_SOURCE_HOME) = newVal +End Property + +Public Property Let InstallHome(newVal$) + data_.Cells(row_, S_P_INSTALL_HOME) = newVal +End Property + +Public Property Let Status(newVal As TBuildStatus) + data_.Cells(row_, S_P_STATUS) = newVal +End Property + +Public Property Let BuildDate(newVal As Double) + data_.Cells(row_, S_P_LAST_BUILD) = newVal +End Property + +Public Property Let IsDistributed(newVal As Boolean) + data_.Cells(row_, S_P_DISTRIBUTE) = IIf(newVal, "1", "0") +End Property + +Public Property Let InstallDate(newVal As Double) + data_.Cells(row_, S_P_LAST_INSTALL) = newVal +End Property diff --git a/src/builder/database/IteratorSharedModule.cls b/src/builder/database/IteratorSharedModule.cls new file mode 100644 index 0000000..8c4672a --- /dev/null +++ b/src/builder/database/IteratorSharedModule.cls @@ -0,0 +1,105 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "IteratorSharedModule" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Public row_ As Long +Private data_ As Excel.Worksheet + +Public Sub Init(target As Excel.Worksheet, Optional tRow& = FIRST_ROW) + Set data_ = target + row_ = tRow +End Sub + +Public Function Increment(Optional inc& = 1) + If row_ + inc > 0 Then _ + row_ = row_ + inc +End Function + +Public Function GoFirst() + row_ = FIRST_ROW +End Function + +Public Function GoLast() + row_ = data_.Columns(S_S_NAME).Find(vbNullString, LookAt:=xlWhole).Row - 1 +End Function + +Public Function IsDone() As Boolean + IsDone = data_.Cells(row_, S_S_NAME) = vbNullString +End Function + +Public Function RemoveRow() + Call data_.Rows(row_).Delete +End Function + +'===== Propertiy Get ===== +Public Property Get Name() As String + Name = data_.Cells(row_, S_S_NAME) +End Property + +Public Property Get ModuleType() As VBIDE.vbext_ComponentType + ModuleType = data_.Cells(row_, S_S_TYPE) +End Property + +Public Property Get Path() As String + Path = data_.Cells(row_, S_S_PATH) +End Property + +Public Property Get Version() As String + Version = data_.Cells(row_, S_S_VERSION) +End Property + +Public Property Get LoC() As Long + LoC = data_.Cells(row_, S_S_LINES_TOTAL) +End Property + +Public Property Get Declarations() As Long + Declarations = data_.Cells(row_, S_S_LINES_DECLARATIONS) +End Property + +Public Property Get CountAPI() As Long + CountAPI = data_.Cells(row_, S_S_API_COUNT) +End Property + +Public Property Get UsedIn() As String + UsedIn = data_.Cells(row_, S_S_USAGE) +End Property + +' ==== Property Let ==== +Public Property Let Name(newVal$) + data_.Cells(row_, S_S_NAME) = newVal +End Property + +Public Property Let ModuleType(newVal As VBIDE.vbext_ComponentType) + data_.Cells(row_, S_S_TYPE) = newVal +End Property + +Public Property Let Path(newVal$) + data_.Cells(row_, S_S_PATH) = newVal +End Property + +Public Property Let Version(newVal$) + data_.Cells(row_, S_S_VERSION) = newVal +End Property + +Public Property Let LoC(newVal&) + data_.Cells(row_, S_S_LINES_TOTAL) = newVal +End Property + +Public Property Let Declarations(newVal&) + data_.Cells(row_, S_S_LINES_DECLARATIONS) = newVal +End Property + +Public Property Let CountAPI(newVal&) + data_.Cells(row_, S_S_API_COUNT) = newVal +End Property + +Public Property Let UsedIn(newVal$) + data_.Cells(row_, S_S_USAGE) = newVal +End Property diff --git a/src/builder/database/IteratorTest.cls b/src/builder/database/IteratorTest.cls new file mode 100644 index 0000000..e05e4cb --- /dev/null +++ b/src/builder/database/IteratorTest.cls @@ -0,0 +1,118 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "IteratorTest" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Public row_ As Long +Private data_ As Excel.Worksheet + +Public Sub Init(target As Excel.Worksheet, Optional tRow& = FIRST_ROW) + Set data_ = target + row_ = tRow +End Sub + +Public Function Increment(Optional inc& = 1) + If row_ + inc > 0 Then _ + row_ = row_ + inc +End Function + +Public Function GoFirst() + row_ = FIRST_ROW +End Function + +Public Function GoLast() + row_ = data_.Columns(S_T_PRODUCT).Find(vbNullString, LookAt:=xlWhole).Row - 1 +End Function + +Public Function IsDone() As Boolean + IsDone = data_.Cells(row_, S_T_PRODUCT) = vbNullString +End Function + +Public Function RemoveRow() + Call data_.Rows(row_).Delete +End Function + +Public Function PushTestResult(sSuite$, sTest$, bResult As Boolean, sMsg$, dDuration As Double) + TestSuite = sSuite + TestName = sTest + Status = bResult + Message = sMsg + DDate = Now + Duration = dDuration + + Call Increment + Product = data_.Cells(row_ - 1, S_T_PRODUCT) + Component = data_.Cells(row_ - 1, S_T_COMPONENT) +End Function + +'===== Propertiy Get ===== +Public Property Get Product() As String + Product = data_.Cells(row_, S_T_PRODUCT) +End Property + +Public Property Get Component() As String + Component = data_.Cells(row_, S_T_COMPONENT) +End Property + +Public Property Get TestSuite() As String + TestSuite = data_.Cells(row_, S_T_SUITE) +End Property + +Public Property Get TestName() As String + TestName = data_.Cells(row_, S_T_TEST) +End Property + +Public Property Get DDate() As Double + DDate = data_.Cells(row_, S_T_DATE) +End Property + +Public Property Get Duration() As Double + DDate = data_.Cells(row_, S_T_DURATION) +End Property + +Public Property Get Status() As Boolean + Status = data_.Cells(row_, S_T_STATUS) = 1 +End Property + +Public Property Get Message() As String + Message = data_.Cells(row_, S_T_MESSAGE) +End Property + +' ==== Property Let ==== +Public Property Let Product(newVal$) + data_.Cells(row_, S_T_PRODUCT) = newVal +End Property + +Public Property Let Component(newVal$) + data_.Cells(row_, S_T_COMPONENT) = newVal +End Property + +Public Property Let TestSuite(newVal$) + data_.Cells(row_, S_T_SUITE) = newVal +End Property + +Public Property Let TestName(newVal$) + data_.Cells(row_, S_T_TEST) = newVal +End Property + +Public Property Let DDate(newVal As Double) + data_.Cells(row_, S_T_DATE) = newVal +End Property + +Public Property Let Duration(newVal As Double) + data_.Cells(row_, S_T_DURATION) = newVal +End Property + +Public Property Let Status(newVal As Boolean) + data_.Cells(row_, S_T_STATUS) = IIf(newVal, 1, 0) +End Property + +Public Property Let Message(newVal$) + data_.Cells(row_, S_T_MESSAGE) = newVal +End Property diff --git a/src/builder/z_UIMessages.bas b/src/builder/z_UIMessages.bas new file mode 100644 index 0000000..25b1b17 --- /dev/null +++ b/src/builder/z_UIMessages.bas @@ -0,0 +1,129 @@ +Attribute VB_Name = "z_UIMessages" +' Messaging module +Option Private Module +Option Explicit + +Public Enum MsgCode + MSG_OK = 0 + + EM_INVALID_SELECTION + EM_SELF_MODIFICATION + EM_IMPORT_FAILED + EM_EXPORT_FAILED + EM_MANIFEST_INVALID_FORMAT + EM_INVALID_COMPONENT + EM_SKELETON_FAILED + EM_MISSING_MAKEFILE + EM_MISSING_PRODUCT + EM_MISSING_MANIFEST + EM_INVALID_PRODUCT + EM_MISSING_FILE + EM_TESTS_FAILED + EM_MAKEFILE_MISSING + EM_MAKEFILE_LOADING_FAILED + EM_PRODUCT_ALREADY_EXISTS + + IM_MAKEFILE_OK + IM_MAKEFILE_FAILED + IM_MANIFEST_OK + IM_MANIFEST_FAILED + IM_COMPILATION_OK + IM_COMPILATION_FAILED + IM_BUILD_OK + IM_BUILD_FAILED + IM_INSTALL_OK + IM_INSTALL_FAILED + IM_RELOAD_OK + IM_RELOAD_FAILED + IM_TESTS_NOT_FOUND + IM_TESTS_COMPLETE + IM_UPDATE_OK + IM_UPDATE_FAILED + IM_SHARED_INVALID + IM_PRODUCT_ADDED + IM_BATCH_BUILD + IM_BATCH_INSTALL + IM_SCAN_OK + IM_UPDATE_SHARED_OK + IM_UPDATE_SHARED_FAILED + + QM_CODE_DELETE_CONFIRM +End Enum + +Private g_UI As API_UserInteraction + +Public Function UserInteraction() As API_UserInteraction + If g_UI Is Nothing Then _ + Set g_UI = New API_UserInteraction + Set UserInteraction = g_UI +End Function + +Public Function SetUserInteraction(newUI As API_UserInteraction) + Set g_UI = newUI +End Function + +Public Function UIShowMessage(theCode As MsgCode, ParamArray params() As Variant) + Dim unwrapped As Variant: unwrapped = params + unwrapped = FixForwardedParams(unwrapped) + + Select Case theCode + Case EM_INVALID_SELECTION: Call MsgBox("Invalid selection, please select single data item", vbExclamation) + Case EM_SELF_MODIFICATION: Call MsgBox("Cannot self-modify" & vbNewLine & "Please use renamed copy of this Workbook", vbExclamation) + Case EM_IMPORT_FAILED: Call MsgBox("Import failed", vbExclamation) + Case EM_EXPORT_FAILED: Call MsgBox("Export failed", vbExclamation) + Case EM_MANIFEST_INVALID_FORMAT: Call MsgBox("Invalid manifest format", vbExclamation) + Case EM_INVALID_COMPONENT: Call MsgBox("Cannot access selected component", vbExclamation) + Case EM_INVALID_PRODUCT: Call MsgBox("Cannot access selected product", vbExclamation) + Case EM_SKELETON_FAILED: Call MsgBox("Unable to produce skeleton", vbExclamation) + Case EM_MISSING_MAKEFILE: Call MsgBox(Fmt("Missing makefile: {1}", unwrapped), vbExclamation) + Case EM_MISSING_PRODUCT: Call MsgBox(Fmt("Missing product data entry: {1}", unwrapped), vbExclamation) + Case EM_MISSING_MANIFEST: Call MsgBox(Fmt("Missing manifest file: {1}", unwrapped), vbExclamation) + Case EM_MISSING_FILE: Call MsgBox(Fmt("Missing target file: {1}", unwrapped), vbExclamation) + Case EM_TESTS_FAILED: Call MsgBox("Failed to run the tests. See log output for more info", vbExclamation) + Case EM_MAKEFILE_MISSING: Call MsgBox(Fmt("Missing makefile: {1}", unwrapped), vbExclamation) + Case EM_MAKEFILE_LOADING_FAILED: Call MsgBox("Cannot load makefile contents", vbExclamation) + Case EM_PRODUCT_ALREADY_EXISTS: Call MsgBox(Fmt("Product with this name already exists: ", unwrapped), vbExclamation) + + Case IM_MAKEFILE_OK: Call MsgBox("Makefile is valid", vbInformation) + Case IM_MAKEFILE_FAILED: Call MsgBox(Fmt("Makefile is not valid!" & vbNewLine & vbNewLine & "{1}", unwrapped), vbExclamation) + Case IM_MANIFEST_OK: Call MsgBox("Manifest is valid", vbInformation) + Case IM_MANIFEST_FAILED: Call MsgBox(Fmt("Manifest is not valid!" & vbNewLine & vbNewLine & "{1}", unwrapped), vbExclamation) + Case IM_COMPILATION_OK: Call MsgBox("All components are compilable", vbInformation) + Case IM_COMPILATION_FAILED: Call MsgBox(Fmt("Some components are broken!" & vbNewLine & vbNewLine & "{1}", unwrapped), vbExclamation) + Case IM_RELOAD_OK: Call MsgBox("All components updated", vbInformation) + Case IM_RELOAD_FAILED: Call MsgBox(Fmt("Some components could not be updated!" & vbNewLine & vbNewLine & "{1}", unwrapped), vbExclamation) + Case IM_BUILD_OK: Call MsgBox(Fmt("Build successfull" & vbNewLine & "Time elapsed: {1}", unwrapped), vbInformation) + Case IM_BUILD_FAILED: Call MsgBox("Build failed. See debug output", vbExclamation) + Case IM_INSTALL_OK: Call MsgBox(Fmt("Installationg successfull" & vbNewLine & "Time elapsed: {1}", unwrapped), vbInformation) + Case IM_INSTALL_FAILED: Call MsgBox("Installation failed. See debug output", vbExclamation) + Case IM_TESTS_NOT_FOUND: Call MsgBox("No tests available for this component", vbInformation) + Case IM_UPDATE_OK: Call MsgBox("Target update success", vbInformation) + Case IM_UPDATE_FAILED: Call MsgBox("Target update failed", vbExclamation) + Case IM_UPDATE_SHARED_OK: Call MsgBox(Fmt("Target shared modules updated: {1}" & vbNewLine & "{2}", unwrapped), vbInformation) + Case IM_UPDATE_SHARED_FAILED: Call MsgBox("Failed to compile after update", vbExclamation) + Case IM_SHARED_INVALID: Call MsgBox(Fmt("Unknown shared modules" & vbNewLine & "{1}", unwrapped), vbExclamation) + Case IM_PRODUCT_ADDED: Call MsgBox(Fmt("Product added: {1}", unwrapped), vbInformation) + Case IM_BATCH_INSTALL: Call MsgBox(Fmt("Install all products complete: {1} / {2}" & vbNewLine & "Time elapsed: {3}", unwrapped), vbInformation) + Case IM_BATCH_BUILD: Call MsgBox(Fmt("Build all products complete: {1} / {2}" & vbNewLine & "Time elapsed: {3}", unwrapped), vbInformation) + Case IM_SCAN_OK: Call MsgBox(Fmt("Scanning makefiles complete. Found {1} new products", unwrapped), vbInformation) + Case IM_TESTS_COMPLETE + Call MsgBox(Fmt("Testing finished: {1} | {2} / {3}" & vbNewLine & "Time elapsed: {4}", unwrapped), IIf(unwrapped(2) = 0, vbInformation, vbExclamation)) + + Case Else: Call MsgBox("Invalid message code", vbCritical) + End Select +End Function + +Public Function UIAskQuestion(theCode As MsgCode, ParamArray params() As Variant) As Boolean + Dim unwrapped As Variant: unwrapped = params + unwrapped = FixForwardedParams(unwrapped) + + Dim answer&: answer = vbNo + Select Case theCode + Case QM_CODE_DELETE_CONFIRM + answer = MsgBox("Are you sure you want to delete ALL macros from target file?", vbYesNo + vbQuestion) + + Case Else + Call MsgBox("Invalid message code", vbCritical) + End Select + UIAskQuestion = answer = vbYes +End Function diff --git a/src/builder/z_UIRibbon.bas b/src/builder/z_UIRibbon.bas new file mode 100644 index 0000000..24ec5eb --- /dev/null +++ b/src/builder/z_UIRibbon.bas @@ -0,0 +1,62 @@ +Attribute VB_Name = "z_UIRibbon" +Option Private Module +Option Explicit + +Public Sub OnRibbonBtn(iControl As IRibbonControl) + Select Case iControl.ID + Case "EditVBAMake": Call RunEditVBAMake + Case "EditManifect": Call RunEditManifect + Case "OpenSrc": Call RunOpenSrc + Case "OpenArtifact": Call RunOpenArtifact + + Case "TestVBAMake": Call RunTestVBAMake + Case "BuildProduct": Call RunBuildProduct + Case "InstallProduct": Call RunInstallProduct + Case "AddProduct": Call RunAddProduct + Case "ArchiveVersion": Call RunArchiveVersion + + Case "TestManifest": Call RunTestManifest + Case "TestComponent": Call RunTestComponent + Case "BuildComponent": Call RunBuildComponent + Case "UpdateSkeleton": Call RunUpdateSkeleton + Case "OpenComponent": Call RunOpenComponent + Case "OpenComponentRO": Call RunOpenComponentReadOnly + + Case "ExportCode": Call RunExportCode + Case "ExportShared": Call RunExportShared + Case "ReloadCode": Call RunReloadCode + Case "UpdateShared": Call RunReloadShared + Case "ListRefs": Call RunListRefs + Case "ListModules": Call RunListModules + + Case "ReloadAllShared": Call RunReloadAllShared + Case "CompileAll": Call RunCompileAll + Case "RunAllTests": Call RunAllTests + Case "BuildAll": Call RunBuildAll + Case "InstallAll": Call RunInstallAll + Case "ScanFolder": Call RunScanFolder + + Case "TargetUpdateAll": Call RunUpdateTarget + Case "TargetUpdateShared": Call RunTargetUpdateShared + Case "TargetClearCode": Call RunTargetClearCode + + Case "OpenLog": Call RunOpenLog + Case "OpenConfig": Call RunOpenConfig + Case "OpenVBCommons": Call RunOpenVBCommons + Case "OpenAppData": Call RunOpenAppData + Case "OpenUserProfile": Call RunOpenUserProfile + Case "OpenConcept": Call RunOpenConcept + Case "OpenCIHT": Call RunOpenCIHT + Case "OpenInstall": Call RunOpenInstall + + Case "ListShared": Call RunListShared + End Select +End Sub + +Public Sub OnDefaultBuild(iControl As IRibbonControl, ByRef vVal) + vVal = Globals.build_ +End Sub + +Public Sub OnBuildTypeChange(iControl As IRibbonControl, idLabel$, nIndex As Variant) + Globals.build_ = nIndex +End Sub diff --git a/src/commons/Declarations.bas b/src/commons/Declarations.bas new file mode 100644 index 0000000..c8583e3 --- /dev/null +++ b/src/commons/Declarations.bas @@ -0,0 +1,4 @@ +Attribute VB_Name = "Declarations" +Option Private Module +Option Explicit + diff --git a/src/commons/DevHelper.bas b/src/commons/DevHelper.bas new file mode 100644 index 0000000..ad60c79 --- /dev/null +++ b/src/commons/DevHelper.bas @@ -0,0 +1,59 @@ +Attribute VB_Name = "DevHelper" +Option Explicit + +Public Const TEST_TARGET_FOLDER = "testFiles" +Public Const TEST_ARTIFACT_FOLDER = "test" + +Public Const TEST_WORD_TEMPLATE = "testTemplate.dotx" +Public Const TEST_WORD_DOCUMENT = "TestWord.docx" +Public Const TEST_WORD_INVALID = "TestInvalid.docx" + +Public Function Dev_PrepareSkeleton() + ' Do nothing +End Function + +Public Sub Dev_ManualRunTest() + Dim sSuite$: sSuite = "s_Path" + Dim sTest$: sTest = "t_ToLocal" + Dim sMsg$: sMsg = Dev_RunTestDebug(sSuite, sTest) + Debug.Print sMsg + Call MsgBox(sMsg) +End Sub + +Public Function Dev_GetTestSuite(sName$) As Object + Select Case sName + Case "s_Config": Set Dev_GetTestSuite = New s_Config + Case "s_JSON": Set Dev_GetTestSuite = New s_JSON + Case "s_Path": Set Dev_GetTestSuite = New s_Path + Case "s_CompoundIntervals": Set Dev_GetTestSuite = New s_CompoundIntervals + Case "s_ExColor": Set Dev_GetTestSuite = New s_ExColor + Case "s_StaticHierarchy": Set Dev_GetTestSuite = New s_StaticHierarchy + Case "s_ParseDate": Set Dev_GetTestSuite = New s_ParseDate + Case "s_ExCollection": Set Dev_GetTestSuite = New s_ExCollection + Case "s_ExVBA": Set Dev_GetTestSuite = New s_ExVBA + Case "s_ExHash": Set Dev_GetTestSuite = New s_ExHash + Case "s_Logger": Set Dev_GetTestSuite = New s_Logger + Case "s_WordWrapper": Set Dev_GetTestSuite = New s_WordWrapper + Case "s_XLWrapper": Set Dev_GetTestSuite = New s_XLWrapper + Case "s_VsoWrapper": Set Dev_GetTestSuite = New s_VsoWrapper + Case "s_ExWinAPI": Set Dev_GetTestSuite = New s_ExWinAPI + Case "s_Graph": Set Dev_GetTestSuite = New s_Graph + End Select +End Function + +Public Function Dev_GetTestFolder() As String + Static sFolder$ + If sFolder = vbNullString Then + sFolder = ThisWorkbook.Path & "\" & TEST_TARGET_FOLDER + End If + Dev_GetTestFolder = sFolder +End Function + +Public Function Dev_GetArtifactFolder() As String + Static sFolder$ + If sFolder = vbNullString Then + sFolder = ThisWorkbook.Path & "\" & TEST_ARTIFACT_FOLDER + End If + Dev_GetArtifactFolder = sFolder +End Function + diff --git a/src/commons/Main.bas b/src/commons/Main.bas new file mode 100644 index 0000000..e50484d --- /dev/null +++ b/src/commons/Main.bas @@ -0,0 +1,3 @@ +Attribute VB_Name = "Main" +Option Private Module +Option Explicit diff --git a/src/commons/MainImpl.bas b/src/commons/MainImpl.bas new file mode 100644 index 0000000..50e4abe --- /dev/null +++ b/src/commons/MainImpl.bas @@ -0,0 +1,4 @@ +Attribute VB_Name = "MainImpl" +Option Private Module +Option Explicit + diff --git a/src/test/TestCustomObject.cls b/src/test/TestCustomObject.cls new file mode 100644 index 0000000..45c0dea --- /dev/null +++ b/src/test/TestCustomObject.cls @@ -0,0 +1,20 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "TestCustomObject" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Public data_ As Double + +Public Function Compare(rhs As Variant) As Double + Compare = data_ - rhs.data_ +End Function + +Public Function ToString(Optional nIndent& = 0) As String + ToString = data_ +End Function diff --git a/src/test/s_CompoundIntervals.cls b/src/test/s_CompoundIntervals.cls new file mode 100644 index 0000000..93698ef --- /dev/null +++ b/src/test/s_CompoundIntervals.cls @@ -0,0 +1,119 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "s_CompoundIntervals" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Private intervals_ As CDS_CompoundIntervals + +Public Function Setup() + ' Mandatory setup function + Set intervals_ = New CDS_CompoundIntervals +End Function + +Public Function Teardown() + ' Mandatory teardown function +End Function + +Public Function t_IsEmpty() + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Empty") + Call Dev_ExpectEQ(0, intervals_.Count) + Call Dev_ExpectTrue(intervals_.IsEmpty) + + Call Dev_NewCase("Not Empty") + Call intervals_.AddItem(2, 2) + Call Dev_ExpectEQ(1, intervals_.Count) + Call Dev_ExpectFalse(intervals_.IsEmpty) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_AddInverval() + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Invalid interval") + Call intervals_.AddItem(1, -1) + Call Dev_ExpectEQ(0, intervals_.Count) + + Call Dev_NewCase("Zero length") + Call intervals_.AddItem(2, 2) + Call Dev_AssertEQ(1, intervals_.Count) + Call Dev_ExpectEQ(Interval(2, 2), intervals_.items_(1)) + + Call Dev_NewCase("Duplicate") + Call intervals_.AddItem(2, 2) + Call Dev_ExpectEQ(1, intervals_.Count) + + Call Dev_NewCase("Generic interval ordering") + Call intervals_.AddItem(3, 4) + Call intervals_.AddItem(-1, 0) + Call Dev_AssertEQ(3, intervals_.Count) + Call Dev_ExpectEQ(Interval(-1, 0), intervals_.items_(1)) + Call Dev_ExpectEQ(Interval(2, 2), intervals_.items_(2)) + Call Dev_ExpectEQ(Interval(3, 4), intervals_.items_(3)) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_Compounding() + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Absorb zero length") + Call intervals_.AddItem(2, 2) + Call intervals_.AddItem(1, 2) + Call Dev_AssertEQ(1, intervals_.Count) + Call Dev_ExpectEQ(Interval(1, 2), intervals_.items_(1)) + Call intervals_.AddItem(1, 1) + Call Dev_ExpectEQ(1, intervals_.Count) + + Call Dev_NewCase("Merge borders") + Call intervals_.Clear + Call intervals_.AddItem(1, 2) + Call intervals_.AddItem(3, 4) + Call intervals_.AddItem(2, 3) + Call Dev_AssertEQ(1, intervals_.Count) + Call Dev_ExpectEQ(Interval(1, 4), intervals_.items_(1)) + + Call Dev_NewCase("Absorb nested") + Call intervals_.Clear + Call intervals_.AddItem(1, 4) + Call intervals_.AddItem(2, 3) + Call Dev_AssertEQ(1, intervals_.Count) + Call Dev_ExpectEQ(Interval(1, 4), intervals_.items_(1)) + + Call Dev_NewCase("Merge intersecting") + Call intervals_.Clear + Call intervals_.AddItem(1, 3) + Call intervals_.AddItem(2, 4) + Call Dev_AssertEQ(1, intervals_.Count) + Call Dev_ExpectEQ(Interval(1, 4), intervals_.items_(1)) + + Call Dev_NewCase("Merge multiple") + Call intervals_.Clear + Call intervals_.AddItem(1, 3) + Call intervals_.AddItem(4, 6) + Call intervals_.AddItem(2, 5) + Call Dev_AssertEQ(1, intervals_.Count) + Call Dev_ExpectEQ(Interval(1, 6), intervals_.items_(1)) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +' ====== +Private Function Interval(nStart&, nFinish&) As CDS_Interval + Dim iNewItem As New CDS_Interval: Call iNewItem.Init(nStart, nFinish) + Set Interval = iNewItem +End Function diff --git a/src/test/s_Config.cls b/src/test/s_Config.cls new file mode 100644 index 0000000..0b87243 --- /dev/null +++ b/src/test/s_Config.cls @@ -0,0 +1,105 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "s_Config" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Private fso_ As Scripting.FileSystemObject +Private config_ As API_Config + +Public Function Setup() + ' Mandatory setup function + Set fso_ = New Scripting.FileSystemObject + Set config_ = New API_Config + Call EnsureFolderExists(Dev_GetTestFolder) +End Function + +Public Function Teardown() + ' Mandatory teardown function + Call fso_.DeleteFolder(Dev_GetTestFolder) +End Function + +Public Function t_BasicDictionary() + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Empty config") + Call Dev_ExpectFalse(config_.Contains("")) + Call Dev_ExpectFalse(config_.Contains("1")) + Call Dev_ExpectEQ(42, config_.GetValue("1", 42)) + Call Dev_ExpectEQ("", config_.GetValue("1")) + + Call Dev_NewCase("Basic type") + Call config_.SetValue("1", 42) + Call Dev_ExpectTrue(config_.Contains("1")) + Call Dev_ExpectEQ(42, config_.GetValue("1")) + Call config_.Clear + Call Dev_ExpectFalse(config_.Contains("1")) + + Call Dev_NewCase("Double set") + Call config_.SetValue("1", 1) + Call config_.SetValue("1", 2) + Call Dev_ExpectEQ(2, config_.GetValue("1")) + Call config_.Clear + + Call Dev_NewCase("Object type") + Call config_.SetValue("1", CColl(1, 2)) + Call Dev_ExpectEQ(CColl(1, 2), config_.GetValue("1")) + Call Dev_ExpectEQ(CColl(1, 2, 3), config_.GetValue("2", CColl(1, 2, 3))) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_FileIO() + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Save to file") + Call config_.SetValue("0", "") + Call config_.SetValue("1", 1) + Call config_.SetValue("2", "42") + Call config_.SetValue("3", CColl(1, 2)) + Dim sTestFile$: sTestFile = Dev_GetTestFolder & "\test.json" + Call Dev_AssertTrue(config_.SaveToFile(sTestFile)) + + Call Dev_NewCase("Load from file") + Dim iLoad As New API_Config + Call Dev_AssertTrue(iLoad.LoadFromFile(sTestFile)) + Call Dev_ExpectEQ(config_.GetValue("0"), iLoad.GetValue("0")) + Call Dev_ExpectEQ(config_.GetValue("1"), iLoad.GetValue("1")) + Call Dev_ExpectEQ(config_.GetValue("2"), iLoad.GetValue("2")) + Call Dev_ExpectEQ(config_.GetValue("3"), iLoad.GetValue("3")) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_JSON_IO() + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Save and Load JSON") + Call config_.SetValue("0", "") + Call config_.SetValue("1", 1) + Call config_.SetValue("2", "42") + Call config_.SetValue("3", CColl(1, 2)) + Call config_.SetValue("4", True) + Dim sJson$: sJson = config_.SaveToJSON() + + Dim iLoad As New API_Config + Call Dev_AssertTrue(iLoad.LoadFromJSON(sJson)) + Call Dev_ExpectEQ(config_.GetValue("0"), iLoad.GetValue("0")) + Call Dev_ExpectEQ(config_.GetValue("1"), iLoad.GetValue("1")) + Call Dev_ExpectEQ(config_.GetValue("2"), iLoad.GetValue("2")) + Call Dev_ExpectEQ(config_.GetValue("3"), iLoad.GetValue("3")) + Call Dev_ExpectEQ(config_.GetValue("4"), iLoad.GetValue("4")) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function diff --git a/src/test/s_ExCollection.cls b/src/test/s_ExCollection.cls new file mode 100644 index 0000000..d1f54ce --- /dev/null +++ b/src/test/s_ExCollection.cls @@ -0,0 +1,237 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "s_ExCollection" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +' ===== Test suite for date Parsing ======= +Option Explicit + +Public Function Setup() + ' Mandatory setup function +End Function + +Public Function Teardown() + ' Mandatory teardown function +End Function + +Public Function t_InCollection() + On Error GoTo PROPAGATE_ERROR + + Dim aCol As Collection + Dim sKey$: sKey = "test" + Call Dev_ExpectFalse(InCollection(sKey, Nothing), "Nothing") + Call Dev_ExpectFalse(InCollection(sKey, aCol), "Undefined") + + Set aCol = New Collection + Call Dev_ExpectFalse(InCollection(sKey, aCol), "Empty") + + Dim nItem& + For nItem = 1 To 10 Step 1 + Call aCol.Add("", sKey & VBA.Int(VBA.Rnd * 1000)) + Next nItem + Call Dev_ExpectFalse(InCollection(sKey, aCol), "False") + + Call aCol.Add("", sKey) + Call Dev_ExpectTrue(InCollection(sKey, aCol), "True") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_IsArrayAllocated() + On Error GoTo PROPAGATE_ERROR + + Dim tArray() As String + + Call Dev_ExpectFalse(IsArrayAllocated(0), "Non-array basic type") + Call Dev_ExpectFalse(IsArrayAllocated(Nothing), "Nothing") + Call Dev_ExpectFalse(IsArrayAllocated(Me), "Object") + Call Dev_ExpectFalse(IsArrayAllocated(tArray), "No allocated") + + ReDim tArray(1 To 10) + Call Dev_ExpectTrue(IsArrayAllocated(tArray), "Allocated") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_IsSubsetOf() + On Error GoTo PROPAGATE_ERROR + + Dim aCol1 As New Collection + Dim aCol2 As New Collection + + Call Dev_ExpectFalse(IsSubsetOf(Nothing, Nothing), "Nothing is not subset of nothing") + Call Dev_ExpectFalse(IsSubsetOf(Nothing, aCol2), "Nothing is not subset of anything") + Call Dev_ExpectFalse(IsSubsetOf(aCol1, Nothing), "Anything is not subset of Nothing") + Call Dev_ExpectTrue(IsSubsetOf(aCol1, aCol2), "Empty is subset of Empty") + + Dim sKey1$: sKey1 = "test1" + Dim sKey2$: sKey2 = "test2" + Dim sKey3$: sKey3 = "test3" + Call aCol2.Add(sKey1, sKey1) + Call aCol2.Add(sKey2, sKey2) + Call Dev_ExpectTrue(IsSubsetOf(aCol1, aCol2), "Empty is subset of Anything") + + Call aCol1.Add(sKey1, sKey1) + Call Dev_ExpectTrue(IsSubsetOf(aCol1, aCol2), "Valid subset") + + Call aCol1.Add(sKey1, sKey2) + Call Dev_ExpectTrue(IsSubsetOf(aCol1, aCol2), "Self subset") + + Call aCol1.Add(sKey3, sKey3) + Call Dev_ExpectFalse(IsSubsetOf(aCol1, aCol2), "Not subset") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_SafeAddToCollection() + On Error GoTo PROPAGATE_ERROR + + Dim aCol As Collection + Dim sKey$: sKey = "test" + Call Dev_ExpectFalse(SafeAddToCollection("", sKey, Nothing), "Nothing") + Call Dev_ExpectFalse(SafeAddToCollection("", sKey, aCol), "Undefined") + + Call Dev_NewCase("Valid add") + Set aCol = New Collection + Call Dev_ExpectTrue(SafeAddToCollection("", sKey, aCol)) + Call Dev_ExpectTrue(InCollection(sKey, aCol)) + Call Dev_ExpectEQ(1, aCol.Count) + + Call Dev_NewCase("Add duplicate") + Call Dev_ExpectFalse(SafeAddToCollection("", sKey, aCol)) + Call Dev_ExpectTrue(InCollection(sKey, aCol)) + Call Dev_ExpectEQ(1, aCol.Count) + + Call Dev_NewCase("Add second item") + Dim sKey2$: sKey2 = "test2" + Call Dev_ExpectTrue(SafeAddToCollection("", sKey2, aCol)) + Call Dev_ExpectTrue(InCollection(sKey2, aCol)) + Call Dev_ExpectEQ(2, aCol.Count) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_SafeMergeCollection() + On Error GoTo PROPAGATE_ERROR + + Dim aCol1 As New Collection + Dim aCol2 As New Collection + + Call Dev_ExpectFalse(SafeMergeCollection(Nothing, Nothing), "Nothing merge nothing") + Call Dev_ExpectFalse(SafeMergeCollection(Nothing, aCol2), "Nothing merge anything") + Call Dev_ExpectFalse(SafeMergeCollection(aCol1, Nothing), "Anything merge Nothing") + Call Dev_ExpectTrue(SafeMergeCollection(aCol1, aCol2), "Empty merge Empty") + Call Dev_ExpectTrue(SafeMergeCollection(aCol1, aCol1), "Merge self") + + Call Dev_NewCase("Valid merge") + Dim sKey1$: sKey1 = "test1" + Dim sKey2$: sKey2 = "test2" + Dim sKey3$: sKey3 = "test3" + Call aCol1.Add(sKey1, sKey1) + Call aCol1.Add(sKey2, sKey2) + Call aCol2.Add(sKey1, sKey1) + Call aCol2.Add(sKey3, sKey3) + Call Dev_ExpectTrue(SafeMergeCollection(aCol1, aCol2)) + Call Dev_ExpectEQ(2, aCol1.Count) + Call Dev_ExpectEQ(3, aCol2.Count) + Call Dev_ExpectTrue(InCollection(sKey2, aCol2)) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_ArrayConversion() + On Error GoTo PROPAGATE_ERROR + + Dim tArray As Variant + Dim aCol As Collection + + Call Dev_ExpectNothing(FromArray(tArray), "Nothing from undef") + Call Dev_ExpectFalse(IsArrayAllocated(ToArray(Nothing)), "Nothing to undef") + + Call Dev_NewCase("Empty to empty") + Set aCol = New Collection + tArray = ToArray(aCol) + Call Dev_ExpectTrue(IsArrayAllocated(tArray)) + Call Dev_ExpectEQ(UBound(tArray, 1), LBound(tArray, 1)) + + Call Dev_NewCase("Empty from empty") + Set aCol = FromArray(tArray) + Call Dev_AssertNotNothing(aCol) + Call Dev_ExpectEQ(0, aCol.Count) + + Call Dev_NewCase("Valid collection to array") + Set aCol = New Collection + Const nCount& = 10 + Dim nItem& + For nItem = 1 To nCount Step 1 + Dim nVal&: nVal = VBA.Int(VBA.Rnd * 1000) + If Not InCollection(CStr(nVal), aCol) Then _ + Call aCol.Add(nVal, CStr(nVal)) + Next nItem + tArray = ToArray(aCol) + Call Dev_AssertEQ(nCount, UBound(tArray) - LBound(tArray) + 1) + For nItem = 1 To nCount Step 1 + Call Dev_ExpectTrue(InCollection(CStr(tArray(nItem - 1)), aCol)) + Next nItem + + Call Dev_NewCase("Valid collection from array") + Set aCol = Nothing + Set aCol = FromArray(tArray) + Call Dev_ExpectEQ(nCount, UBound(tArray) - LBound(tArray) + 1) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_CollectionToIndex() + On Error GoTo PROPAGATE_ERROR + + Call Dev_ExpectEQ(CSet(), CollectionToIndex(CColl()), "Empty collection") + + Call Dev_NewCase("Unique elements") + Dim indicies As New Scripting.Dictionary + indicies(1337) = 1 + indicies("leet") = 2 + Call Dev_ExpectEQ(indicies, CollectionToIndex(CColl(1337, "leet"))) + + Call Dev_NewCase("Initialized duplicate elements") + Call Dev_ExpectEQ(indicies, CollectionToIndex(CColl(1337, "leet", 1337))) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_RevertCollection() + On Error GoTo PROPAGATE_ERROR + + Call Dev_ExpectEQ(CColl(), RevertCollection(CColl()), "Empty collection") + + Call Dev_NewCase("Unique elements") + Dim indicies As New Scripting.Dictionary + indicies(1337) = 1 + indicies("leet") = 2 + Call Dev_ExpectEQ(CColl("leet", 1337), RevertCollection(CColl(1337, "leet"))) + + Call Dev_NewCase("Palindrom") + Call Dev_ExpectEQ(CColl(1337, "leet", 1337), RevertCollection(CColl(1337, "leet", 1337))) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function diff --git a/src/test/s_ExColor.cls b/src/test/s_ExColor.cls new file mode 100644 index 0000000..078eafb --- /dev/null +++ b/src/test/s_ExColor.cls @@ -0,0 +1,62 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "s_ExColor" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +' TODO: +' Public Function ColorGetRGB(nColorID&, aDocument As Object) As Long +' Public Function QueryColor(nColorID&, aDocument As Object) As ColorDetails + +Public Function Setup() + ' Mandatory setup function +End Function + +Public Function Teardown() + ' Mandatory teardown function +End Function + +Public Function t_GetLuma() + On Error GoTo PROPAGATE_ERROR + + Call Dev_ExpectEQ(255, ColorGetLuma(RGB(255, 255, 255)), "White") + Call Dev_ExpectEQ(0, ColorGetLuma(RGB(0, 0, 0)), "Black") + Call Dev_ExpectEQ(54, ColorGetLuma(RGB(255, 0, 0)), "Red") + Call Dev_ExpectEQ(182, ColorGetLuma(RGB(0, 255, 0)), "Green") + Call Dev_ExpectEQ(18, ColorGetLuma(RGB(0, 0, 255)), "Blue") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_ColorString() + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Invalid input") + Call Dev_ExpectEQ(HC_INVALID, ConvertStringToRGB("99999999999"), "Overflow long") + Call Dev_ExpectEQ(HC_INVALID, ConvertStringToRGB(RGB(255, 255, 255)), "Overflow integer") + Call Dev_ExpectEQ(HC_INVALID, ConvertStringToRGB("RGB(256,0,0)"), "Invalid color value") + Call Dev_ExpectEQ("RGB(255,255,255)", ConvertRGBtoString(&HFFFFFFFF), "Overflow color") + + Call Dev_NewCase("White") + Call Dev_ExpectEQ(RGB(255, 255, 255), ConvertStringToRGB("RGB(255,255,255)")) + Call Dev_ExpectEQ("RGB(255,255,255)", ConvertRGBtoString(RGB(255, 255, 255))) + + Call Dev_NewCase("Black") + Call Dev_ExpectEQ(RGB(0, 0, 0), ConvertStringToRGB("RGB(0,0,0)")) + Call Dev_ExpectEQ("RGB(0,0,0)", ConvertRGBtoString(RGB(0, 0, 0))) + + Call Dev_NewCase("Generic color") + Call Dev_ExpectEQ(RGB(165, 137, 99), ConvertStringToRGB("RGB(165,137,99)")) + Call Dev_ExpectEQ("RGB(55,67,77)", ConvertRGBtoString(RGB(55, 67, 77))) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function diff --git a/src/test/s_ExHash.cls b/src/test/s_ExHash.cls new file mode 100644 index 0000000..42bf850 --- /dev/null +++ b/src/test/s_ExHash.cls @@ -0,0 +1,39 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "s_ExHash" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +' ===== Test suite for Hashing ======= +Option Explicit + +Public Function Setup() + ' Mandatory setup function +End Function + +Public Function Teardown() + ' Mandatory teardown function +End Function + +Public Function t_HashMD5() + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Null string") + Call Dev_ExpectEQ("d41d8cd98f00b204e9800998ecf8427e", MD5AsString(vbNullString)) + Call Dev_ExpectEQ(319274370, MD5AsLong(vbNullString)) + + Call Dev_NewCase("Empty string") + Call Dev_ExpectEQ("d41d8cd98f00b204e9800998ecf8427e", MD5AsString("")) + Call Dev_ExpectEQ(319274370, MD5AsLong("")) + + Call Dev_NewCase("Known long string") + Call Dev_ExpectEQ("a802c7e23a696b5bc43fc002c4ab3361", MD5AsString("Testing long string")) + Call Dev_ExpectEQ(995413151, MD5AsLong("Testing long string")) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function diff --git a/src/test/s_ExVBA.cls b/src/test/s_ExVBA.cls new file mode 100644 index 0000000..77440d8 --- /dev/null +++ b/src/test/s_ExVBA.cls @@ -0,0 +1,645 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "s_ExVBA" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +' ===== Test suite for date Parsing ======= +Option Explicit + +Public Function Setup() + ' Mandatory setup function +End Function + +Public Function Teardown() + ' Mandatory teardown function +End Function + +Public Function t_CreateCollection() + On Error GoTo PROPAGATE_ERROR + + Call Dev_ExpectEQ(New Collection, CColl(), "Empty collection") + + Call Dev_NewCase("Valid collection") + Dim iExpect As New Collection + Call iExpect.Add(1): Call iExpect.Add("13"): Call iExpect.Add(3): Call iExpect.Add(1) + Call Dev_ExpectEQ(iExpect, CColl(1, "13", 3, 1)) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_CSet() + On Error GoTo PROPAGATE_ERROR + + Call Dev_ExpectEQ(New Scripting.Dictionary, CSet(), "Empty set") + + Call Dev_NewCase("Valid set") + Dim iExpect As New Scripting.Dictionary + Call iExpect.Add(1, 0): Call iExpect.Add("13", 0): Call iExpect.Add(3, 0) + Call Dev_ExpectEQ(iExpect, CSet(1, "13", 3)) + + Call Dev_NewCase("Duplicate elements") + Call Dev_ExpectEQ(iExpect, CSet(1, "13", 3, 1, 3)) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_CreateDictionary() + On Error GoTo PROPAGATE_ERROR + + Call Dev_ExpectEQ(New Scripting.Dictionary, CDict(), "Empty dict") + + Call Dev_NewCase("Valid set") + Dim iExpect As New Scripting.Dictionary + Call iExpect.Add(1, 42): Call iExpect.Add("13", 1337): Call iExpect.Add(3, CDict()) + Call Dev_ExpectEQ(iExpect, CDict(1, 42, "13", 1337, 3, CDict())) + + Call Dev_NewCase("Invalid number of args") + On Error Resume Next + Set iExpect = CDict(1, "13", 3) + Call Dev_ExpectAnyError + On Error GoTo PROPAGATE_ERROR + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_UniqueList() + On Error GoTo PROPAGATE_ERROR + + Dim uList As Scripting.Dictionary + + Call Dev_ExpectEQ(0, UniqueList.Count, "Empty list") + + Call Dev_NewCase("Simple list") + Set uList = UniqueList(13, 37, 3) + Call Dev_AssertEQ(3, uList.Count) + Call Dev_ExpectEQ(1, uList(13)) + Call Dev_ExpectEQ(2, uList(37)) + Call Dev_ExpectEQ(3, uList(3)) + + Call Dev_NewCase("Repeating elements") + Set uList = UniqueList(13, 13, 3) + Call Dev_AssertEQ(2, uList.Count) + Call Dev_ExpectEQ(1, uList(13)) + Call Dev_ExpectEQ(3, uList(3)) + + Call Dev_NewCase("Mixed list") + Set uList = UniqueList(13, "37", 3.1) + Call Dev_AssertEQ(3, uList.Count) + Call Dev_ExpectEQ(1, uList(13)) + Call Dev_ExpectEQ(2, uList("37")) + Call Dev_ExpectEQ(3, uList(3.1)) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_ArraySize() + On Error GoTo PROPAGATE_ERROR + + Call Dev_ExpectEQ(VBA_INVALID_SIZE, ArraySize(Nothing, 1), "Nothing") + Call Dev_ExpectEQ(VBA_INVALID_SIZE, ArraySize(1, 1), "Non-array") + Call Dev_ExpectEQ(VBA_INVALID_SIZE, ArraySize(New Collection, 1), "Object") + + Dim unallocArr() As Variant + Call Dev_ExpectEQ(VBA_INVALID_SIZE, ArraySize(unallocArr, 1), "Unallocated") + + Dim arr1() As Variant + ReDim arr1(0 To 4) + Call Dev_ExpectEQ(5, ArraySize(arr1, 1), "1D Array 0-based") + Call Dev_ExpectEQ(VBA_INVALID_SIZE, ArraySize(arr1, 2), "Out of bounds dimension") + Call Dev_ExpectEQ(VBA_INVALID_SIZE, ArraySize(arr1, -1), "Invalid dimension") + + ReDim arr1(1 To 5) + Call Dev_ExpectEQ(5, ArraySize(arr1, 1), "1D Array 1-based") + + Call Dev_NewCase("2D array") + Dim arr2() As Variant + ReDim arr2(1 To 5, 1 To 7) + Call Dev_ExpectEQ(5, ArraySize(arr2, 1)) + Call Dev_ExpectEQ(7, ArraySize(arr2, 2)) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_ArrayToIndex() + On Error GoTo PROPAGATE_ERROR + + Call Dev_ExpectNothing(ArrayToIndex(Array()), "Not allocated") + + Call Dev_NewCase("Not initialized") + Dim arr(1 To 2) As Variant + Dim indicies As New Scripting.Dictionary + indicies(Empty) = 1 + Call Dev_ExpectEQ(indicies, ArrayToIndex(arr)) + + Call Dev_NewCase("Initialized unique elements") + Call indicies.RemoveAll + arr(1) = 1337: indicies(1337) = 1 + arr(2) = "leet": indicies("leet") = 2 + Call Dev_ExpectEQ(indicies, ArrayToIndex(arr)) + + Call Dev_NewCase("Initialized duplicate elements") + Call indicies.RemoveAll + arr(1) = 1337: indicies(1337) = 1 + arr(2) = 1337 + Call Dev_ExpectEQ(indicies, ArrayToIndex(arr)) + + Call Dev_NewCase("Custom base") + Dim arr2(2 To 5) As Variant + Call indicies.RemoveAll + indicies(Empty) = 2 + Call Dev_ExpectEQ(indicies, ArrayToIndex(arr2)) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_IsArrayAllocated() + On Error GoTo PROPAGATE_ERROR + + Call Dev_ExpectFalse(IsArrayAllocated(Nothing), "Nothing") + Call Dev_ExpectFalse(IsArrayAllocated(1), "Non-array") + Call Dev_ExpectFalse(IsArrayAllocated(New Collection), "Object") + + Call Dev_NewCase("Unallocated") + Dim unallocArr() As Variant + Call Dev_ExpectFalse(IsArrayAllocated(unallocArr)) + Call Dev_ExpectNoError + + Dim arr1(1 To 5) As Variant + Call Dev_ExpectTrue(IsArrayAllocated(arr1), "Allocated empty array") + + Dim arr2 As Variant: arr2 = Array(1, 2, 3) + Call Dev_ExpectTrue(IsArrayAllocated(arr2), "Allocated full array") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_FixForwardedParams() + On Error GoTo PROPAGATE_ERROR + + Call Dev_ExpectFalse(IsArrayAllocated(TestForwardParams()), "No params") + Call Dev_ExpectEQ(Array(1), TestForwardParams(1), "Simple arg") + Call Dev_ExpectEQ(Array(1, "123"), TestForwardParams(1, "123"), "Multiple arg") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_CompareApproximate() + On Error GoTo PROPAGATE_ERROR + + On Error Resume Next + Call Dev_ExpectEQ(0, CompareApproximate("a", "b", 0)) + Call Dev_ExpectError(SYS_ERR_TYPE_MISMATCH, "Invalid type comparison") + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Precision equals") + Call Dev_ExpectEQ(0, CompareApproximate(0, 0, 0)) + Call Dev_ExpectEQ(0, CompareApproximate(1, 1, 0)) + Call Dev_ExpectEQ(0, CompareApproximate(1.1, 1.1, 0)) + Call Dev_ExpectNE(0, CompareApproximate(1.1, 1, 1)) + Call Dev_ExpectEQ(0, CompareApproximate(1.1, 1, 0)) + Call Dev_ExpectEQ(0, CompareApproximate(11, 10, -1)) + Call Dev_ExpectEQ(0, CompareApproximate(111, 100, -2)) + Call Dev_ExpectEQ(11, CompareApproximate(111, 100, -1)) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_CompareDeepBasic() + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Basic type equals") + Call Dev_ExpectEQ(0, CompareDeep(True, True)) + Call Dev_ExpectEQ(0, CompareDeep(0, 0)) + Call Dev_ExpectEQ(0, CompareDeep(0&, 0&)) + Call Dev_ExpectEQ(0, CompareDeep(1.1, 1.1)) + Call Dev_ExpectEQ(0, CompareDeep(1.1!, 1.1!)) + Call Dev_ExpectEQ(0, CompareDeep("", "")) + + Call Dev_NewCase("Basic type compare") + Call Dev_ExpectNE(0, CompareDeep(False, True)) + Call Dev_ExpectNE(0, CompareDeep(True, False)) + Call Dev_ExpectEQ(1, CompareDeep(2, 1)) + Call Dev_ExpectEQ(2, CompareDeep(3, 1)) + Call Dev_ExpectEQ(-2, CompareDeep(1, 3)) + Call Dev_ExpectEQ(2, CompareDeep(3&, 1&)) + Call Dev_ExpectEQ(-2, CompareDeep(1&, 3&)) + Call Dev_ExpectAEQ(2, CompareDeep(3.1, 1.1), 0) + Call Dev_ExpectAEQ(-2, CompareDeep(1.1, 3.1), 0) + Call Dev_ExpectAEQ(1.9, CompareDeep(3, 1.1), 1) + Call Dev_ExpectAEQ(2, CompareDeep(3.1!, 1.1!), 0) + Call Dev_ExpectAEQ(-2, CompareDeep(1.1!, 3.1!), 0) + Call Dev_ExpectEQ(2, CompareDeep("3", "1")) + Call Dev_ExpectEQ(-2, CompareDeep("1", "3")) + + Call Dev_NewCase("Basic type cross compare") + Call Dev_ExpectEQ(0, CompareDeep(0, False)) + Call Dev_ExpectEQ(0, CompareDeep(False, 0)) + Call Dev_ExpectEQ(0, CompareDeep(-1, True)) + Call Dev_ExpectEQ(0, CompareDeep(True, -1)) + Call Dev_ExpectEQ(1, CompareDeep(0, "")) + Call Dev_ExpectEQ(1, CompareDeep("", 0)) + Call Dev_ExpectEQ(0, CompareDeep(0, "0")) + Call Dev_ExpectEQ(0, CompareDeep("0", 0)) + Call Dev_ExpectEQ(0, CompareDeep(1, 1&)) + Call Dev_ExpectEQ(0, CompareDeep(1&, 1)) + Call Dev_ExpectEQ(0, CompareDeep(1, 1#)) + Call Dev_ExpectEQ(0, CompareDeep(1#, 1)) + Call Dev_ExpectAEQ(0, CompareDeep(1, 1!), 1) + Call Dev_ExpectAEQ(0, CompareDeep(1!, 1), 1) + Call Dev_ExpectAEQ(0, CompareDeep(1.1, 1.1!), 1) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_CompareDeepObject() + On Error GoTo PROPAGATE_ERROR + + Call Dev_ExpectEQ(0, CompareDeep(Nothing, Nothing), "Nothing compare") + + On Error Resume Next + Call CompareDeep(ThisWorkbook, ThisWorkbook.Application) + Call Dev_ExpectError(SYS_ERR_INVALID_OPERATION, "Mismatched type comparison") + On Error GoTo PROPAGATE_ERROR + + Call Dev_ExpectEQ(0, CompareDeep(Me, Me), "Compare equal object pointers") + Call Dev_ExpectEQ(1, CompareDeep(Me, New s_ExVBA), "Compare different object pointers") + + Call Dev_NewCase("Valid object compare") + Dim obj1 As New TestCustomObject: obj1.data_ = 2 + Dim obj2 As New TestCustomObject: obj2.data_ = 1 + Call Dev_ExpectEQ(1, CompareDeep(obj1, obj2)) + obj2.data_ = 2 + Call Dev_ExpectEQ(0, CompareDeep(obj1, obj2)) + obj2.data_ = 3 + Call Dev_ExpectEQ(-1, CompareDeep(obj1, obj2)) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_CompareDeepCollection() + On Error GoTo PROPAGATE_ERROR + + Dim colEmpty As New Collection + Dim colEmpty2 As New Collection + Dim col1 As New Collection: Call col1.Add(1): Call col1.Add(2) + Dim col2 As New Collection: Call col2.Add(2): Call col2.Add(1) + Dim col3 As New Collection: Call col3.Add(1): Call col3.Add(2) + Dim col4 As New Collection: Call col4.Add(1): Call col4.Add(2): Call col4.Add(3) + + Call Dev_ExpectEQ(0, CompareDeep(colEmpty, colEmpty2), "Empty comparison") + Call Dev_ExpectEQ(0, CompareDeep(colEmpty, colEmpty), "Empty self comparison") + + Call Dev_NewCase("Simple collection comparison") + Call Dev_ExpectEQ(0, CompareDeep(col1, col1)) + Call Dev_ExpectEQ(0, CompareDeep(col1, col3)) + Call Dev_ExpectNE(0, CompareDeep(col1, col2)) + Call Dev_ExpectNE(0, CompareDeep(col1, col4)) + + Call col1.Add("1") + Call col3.Add(1) + Call Dev_ExpectEQ(0, CompareDeep(col1, col3), "Mixed comparison") + + Dim obj1 As New TestCustomObject: obj1.data_ = 1 + Dim obj2 As New TestCustomObject: obj2.data_ = 1 + Call col1.Add(obj1) + Call col3.Add(obj2) + Call Dev_ExpectEQ(0, CompareDeep(col1, col3), "Object collection comparison") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_CompareDeepDictionary() + On Error GoTo PROPAGATE_ERROR + + Dim dictEmpty As New Scripting.Dictionary + Dim dictEmpty2 As New Scripting.Dictionary + Dim d1 As New Scripting.Dictionary: Call d1.Add(1, 1): Call d1.Add(2, 2) + Dim d2 As New Scripting.Dictionary: Call d2.Add(1, 2): Call d2.Add(2, 1) + Dim d3 As New Scripting.Dictionary: Call d3.Add(1, 1): Call d3.Add(2, 2) + Dim d4 As New Scripting.Dictionary: Call d4.Add("1", 1): Call d4.Add("2", 2) + Dim d5 As New Scripting.Dictionary: Call d5.Add(1, 1): Call d5.Add(2, 2): Call d5.Add(3, 3) + + Call Dev_ExpectEQ(0, CompareDeep(dictEmpty, dictEmpty2), "Empty comparison") + Call Dev_ExpectEQ(0, CompareDeep(dictEmpty, dictEmpty), "Empty self comparison") + + Call Dev_NewCase("Dictionary compare") + Call Dev_ExpectEQ(0, CompareDeep(d1, d1)) + Call Dev_ExpectEQ(0, CompareDeep(d1, d3)) + Call Dev_ExpectNE(0, CompareDeep(d1, d2)) + Call Dev_ExpectNE(0, CompareDeep(d1, d4)) + Call Dev_ExpectNE(0, CompareDeep(d1, d5)) + + Call d1.Add(4, "1") + Call d3.Add(4, 1) + Call Dev_ExpectEQ(0, CompareDeep(d1, d3), "Mixed comparison") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_CompareDeepArray() + On Error GoTo PROPAGATE_ERROR + + Dim unallocArr() As Variant + Dim arr1 As Variant: arr1 = Array(1, 2, 3) + Dim arr2 As Variant: arr2 = Array(2, 1, 3) + Dim arr3 As Variant: arr3 = Array(1, 2, 3) + Dim arr4 As Variant: arr4 = Array("1", 2, 3) + Dim arr5 As Variant: arr5 = Array("1", 2, 3, 4) + + Call Dev_ExpectEQ(0, CompareDeep(unallocArr, unallocArr), "Unallocated comparison") + + Call Dev_NewCase("Array compare") + Call Dev_ExpectEQ(0, CompareDeep(arr1, arr1)) + Call Dev_ExpectEQ(0, CompareDeep(arr1, arr3)) + Call Dev_ExpectNE(0, CompareDeep(arr1, arr2)) + Call Dev_ExpectEQ(0, CompareDeep(arr1, arr4)) + Call Dev_ExpectNE(0, CompareDeep(arr1, arr5)) + + Call Dev_NewCase("Compound array compare") + Dim arr6 As Variant: arr6 = Array(Array(1), Array(2, 3), Array("3")) + Call Dev_ExpectEQ(0, CompareDeep(arr6, arr6)) + Call Dev_ExpectNE(0, CompareDeep(arr1, arr6)) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_ExtractTimestamp() + On Error GoTo PROPAGATE_ERROR + + Call Dev_ExpectEQ(0, ExtractTimestamp(""), "Empty string") + Call Dev_ExpectEQ(0, ExtractTimestamp("INVALID"), "Invalid text") + Call Dev_ExpectEQ(0, ExtractTimestamp("2020-13-99"), "Invalid timestamp") + Call Dev_ExpectEQ(DateSerial(2020, 12, 30), ExtractTimestamp("2020-12-30"), "Short date") + Call Dev_ExpectEQ(0, ExtractTimestamp("2020-12-30: TestMsg"), "Short date and Msg") + + Call ExtractTimestamp("2020-13-99") + Call Dev_ExpectNoError("No error polution") + + Call Dev_NewCase("Full time date") + Call Dev_ExpectEQ(CDbl(VBA.TimeValue("11:59:59") + VBA.DateValue("2020-12-30")), ExtractTimestamp("2020-12-30 11:59:59")) + + Call Dev_NewCase("Full time date and Msg") + Call Dev_ExpectEQ(CDbl(VBA.TimeValue("11:59:59") + VBA.DateValue("2020-12-30")), ExtractTimestamp("2020-12-30 11:59:59: TestMsg")) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_TrimTimestamp() + On Error GoTo PROPAGATE_ERROR + + Call Dev_ExpectEQ(vbNullString, TrimTimestamp(""), "Empty string") + Call Dev_ExpectEQ("INVALID", TrimTimestamp("INVALID"), "Invalid text") + Call Dev_ExpectEQ("2020-13-99", TrimTimestamp("2020-13-99"), "Invalid timestamp") + Call Dev_ExpectEQ(vbNullString, TrimTimestamp("2020-12-30"), "Short date") + Call Dev_ExpectEQ("2020-12-30: TestMsg", TrimTimestamp("2020-12-30: TestMsg"), "Short date and Msg") + + Call Dev_NewCase("Full time date") + Call Dev_ExpectEQ(vbNullString, TrimTimestamp("2020-12-30 11:59:59")) + + Call Dev_NewCase("Full time date and Msg") + Call Dev_ExpectEQ("TestMsg", TrimTimestamp("2020-12-30 11:59:59: TestMsg")) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_Fmt() + On Error GoTo PROPAGATE_ERROR + + Call Dev_ExpectEQ(vbNullString, Fmt(vbNullString), "Empty string") + Call Dev_ExpectEQ("{1}", Fmt("{1}"), "Missing arg") + Call Dev_ExpectEQ("{0}", Fmt("{0}", 1337), "Invalid indexing") + Call Dev_ExpectEQ("{2}", Fmt("{2}", 1337), "Missing index marker") + Call Dev_ExpectEQ("1337 12 1337", Fmt("{1} {2} {1}", 1337, 12), "Miltiple markers") + Call Dev_ExpectEQ("1337 12 1337 {3}", Fmt("{1} {2} {1} {3}", 1337, 12), "Partially valid") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_PrintBasic() + On Error GoTo PROPAGATE_ERROR + + Call Dev_ExpectEQ("", PrettyPrint(vbNullString), "Empty string") + Call Dev_ExpectEQ("[Nothing]", PrettyPrint(Nothing), "Nothing") + Call Dev_ExpectEQ("[Null]", PrettyPrint(Null), "Not initialized variant") + Call Dev_ExpectEQ("abc", PrettyPrint("abc"), "String") + Call Dev_ExpectEQ("0", PrettyPrint(0), "Ineger") + Call Dev_ExpectEQ(CStr(3.14), PrettyPrint(3.14), "Float") + Call Dev_ExpectEQ("True", PrettyPrint(True), "Boolean") + + Dim vNotInit As Variant + Call Dev_ExpectEQ("[Empty]", PrettyPrint(vNotInit), "Not initialized variant") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_PrintObject() + On Error GoTo PROPAGATE_ERROR + + Call Dev_ExpectEQ("[Workbook]", PrettyPrint(ThisWorkbook), "Non-stringable type") + Call Dev_ExpectEQ("Microsoft Excel", PrettyPrint(ThisWorkbook.Application), "Stringable type") + + Dim strObject As New TestCustomObject: strObject.data_ = 2 + Call Dev_ExpectEQ("2", PrettyPrint(strObject), "Custom object provides valid callback") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_PrintCollection() + On Error GoTo PROPAGATE_ERROR + + Dim testCol As New Collection + Call Dev_ExpectEQ("$Collection []", PrettyPrint(testCol), "Empty collection") + + Call Dev_NewCase("Basic elements") + Call testCol.Add(1): Call testCol.Add(2) + Dim sResult$ + sResult = "$Collection [" & vbNewLine & _ + Indent(1) & "1," & vbNewLine & _ + Indent(1) & "2" & vbNewLine & _ + "]" + Call Dev_ExpectEQ(sResult, PrettyPrint(testCol)) + + Call Dev_NewCase("Compound elements") + Dim intCol As New Collection: Call intCol.Add(3) + Call testCol.Add(intCol) + sResult = "$Collection [" & vbNewLine & _ + Indent(1) & "1," & vbNewLine & _ + Indent(1) & "2," & vbNewLine & _ + Indent(1) & "$Collection [" & vbNewLine & _ + Indent(2) & "3" & vbNewLine & _ + Indent(1) & "]" & vbNewLine & _ + "]" + Call Dev_ExpectEQ(sResult, PrettyPrint(testCol)) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_PrintDictionary() + On Error GoTo PROPAGATE_ERROR + + Dim testDict As New Scripting.Dictionary + Call Dev_ExpectEQ("$Dictionary []", PrettyPrint(testDict), "Empty dictionary") + + Call Dev_NewCase("Basic elements") + Call testDict.Add(1, "a"): Call testDict.Add(2, "b") + Dim sResult$ + sResult = "$Dictionary [" & vbNewLine & _ + Indent(1) & "1: a," & vbNewLine & _ + Indent(1) & "2: b" & vbNewLine & _ + "]" + Call Dev_ExpectEQ(sResult, PrettyPrint(testDict)) + + Call Dev_NewCase("Compound elements") + Dim intDict As New Scripting.Dictionary: Call intDict.Add(3, "c") + Call testDict.Add(3, intDict) + sResult = "$Dictionary [" & vbNewLine & _ + Indent(1) & "1: a," & vbNewLine & _ + Indent(1) & "2: b," & vbNewLine & _ + Indent(1) & "3: $Dictionary [" & vbNewLine & _ + Indent(2) & "3: c" & vbNewLine & _ + Indent(1) & "]" & vbNewLine & _ + "]" + Call Dev_ExpectEQ(sResult, PrettyPrint(testDict)) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_PrintArray() + On Error GoTo PROPAGATE_ERROR + + Dim testArr() As Variant + Call Dev_ExpectEQ("$Array []", PrettyPrint(testArr), "Unallocated array") + + Call Dev_NewCase("Uninit array") + ReDim testArr(1 To 2) + Dim sResult$ + sResult = "$Array [" & vbNewLine & _ + Indent(1) & "[Empty]," & vbNewLine & _ + Indent(1) & "[Empty]" & vbNewLine & _ + "]" + Call Dev_ExpectEQ(sResult, PrettyPrint(testArr)) + + Call Dev_NewCase("Basic elements") + testArr(1) = 1 + testArr(2) = 2 + sResult = "$Array [" & vbNewLine & _ + Indent(1) & "1," & vbNewLine & _ + Indent(1) & "2" & vbNewLine & _ + "]" + Call Dev_ExpectEQ(sResult, PrettyPrint(testArr)) + + Call Dev_NewCase("Compound elements") + + Set testArr(2) = New Collection + sResult = "$Array [" & vbNewLine & _ + Indent(1) & "1," & vbNewLine & _ + Indent(1) & "$Collection []" & vbNewLine & _ + "]" + Call Dev_ExpectEQ(sResult, PrettyPrint(testArr)) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_CommonPrefixLength() + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Empty") + Call Dev_ExpectEQ(0, CommonPrefixLength("", "")) + Call Dev_ExpectEQ(0, CommonPrefixLength("abc", "")) + Call Dev_ExpectEQ(0, CommonPrefixLength("", "abc")) + + Call Dev_NewCase("Valid prefix") + Call Dev_ExpectEQ(3, CommonPrefixLength("123", "123")) + Call Dev_ExpectEQ(3, CommonPrefixLength("123", "1234")) + Call Dev_ExpectEQ(3, CommonPrefixLength("1234", "123")) + Call Dev_ExpectEQ(11, CommonPrefixLength("C:\Windows\Test", "C:\Windows\Process")) + + Call Dev_NewCase("Compare mode") + Call Dev_ExpectEQ(0, CommonPrefixLength("ABC", "abc", vbBinaryCompare)) + Call Dev_ExpectEQ(3, CommonPrefixLength("ABC", "abc", vbTextCompare)) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_DictionaryAbsorbShallow() + On Error GoTo PROPAGATE_ERROR + + Dim iSource As New Scripting.Dictionary + Dim iDestination As New Scripting.Dictionary + iSource.Item(1) = 1337 + iSource.Item(42) = 43 + iDestination.Item(1) = 42 + Set iSource.Item(2) = CColl(1, 2, 3) + + Call DictionaryAbsorbShallow(iDestination, iSource) + Call Dev_AssertEQ(3, iDestination.Count, "Count elements") + Call Dev_ExpectEQ(43, iDestination(42), "Transfer basic values") + Call Dev_ExpectEQ(42, iDestination(1), "Do not overwrite common keys") + Call Dev_ExpectEQ(CColl(1, 2, 3), iDestination(2), "Transfer complex elements") + + Call Dev_NewCase("Shallow copy") + iSource.Item(42) = 44 + Call iSource.Item(2).Add(4) + Call Dev_ExpectEQ(43, iDestination(42)) + Call Dev_ExpectEQ(CColl(1, 2, 3, 4), iDestination(2)) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +' ===== +Private Function TestForwardParams(ParamArray params() As Variant) As Variant + Dim val As Variant: val = params + TestForwardParams = FixForwardedParams(val) +End Function diff --git a/src/test/s_ExWinAPI.cls b/src/test/s_ExWinAPI.cls new file mode 100644 index 0000000..0cc3da7 --- /dev/null +++ b/src/test/s_ExWinAPI.cls @@ -0,0 +1,341 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "s_ExWinAPI" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +' TODO: +' Public Function ForEachFileRecursive(sTargetFolder$, oCallback As Object, sFuncName$) +' Public Function UnzipFile(sTargetFile$, sDestinationFolder$) As Boolean +' Public Function ZipFolder(sTargetFolder$, sDestinationFile$) As Boolean +' Public Function ListFilesIn(sTargetFolder$, Optional sMask$ = "*.*") As Collection + +Private fso_ As Scripting.FileSystemObject + +Public Function Setup() + ' Mandatory setup function + Set fso_ = New Scripting.FileSystemObject + Call EnsureFolderExists(Dev_GetTestFolder) +End Function + +Public Function Teardown() + ' Mandatory teardown function + Call fso_.DeleteFolder(Dev_GetTestFolder) +End Function + +Public Function t_WindowsTempFolder() + On Error GoTo PROPAGATE_ERROR + + Call Dev_ExpectEQ(Environ("TEMP"), WindowsTempFolder, "Temp folder") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_Clipboard() + On Error GoTo PROPAGATE_ERROR + + Dim sText$: sText = "test" & vbNewLine & "multiline" + Call AddToClipboard(sText) + Call Dev_ExpectEQ(sText, RetrieveFromClipboard, "Multiline text") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_EnsureFolderExists() + On Error GoTo PROPAGATE_ERROR + + On Error Resume Next + Call EnsureFolderExists("") + Call Dev_ExpectNoError("Empty input") + On Error GoTo PROPAGATE_ERROR + + On Error Resume Next + Call EnsureFolderExists("invalid input?") + Call Dev_ExpectNoError("Invalid input") + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Single folder") + Dim sFolder1$: sFolder1 = Dev_GetTestFolder & "\1" + Call EnsureFolderExists(sFolder1) + Call Dev_ExpectTrue(fso_.FolderExists(sFolder1)) + Call fso_.DeleteFolder(sFolder1) + + Call Dev_NewCase("Custom fso") + Call EnsureFolderExists(sFolder1, fso_) + Call Dev_ExpectTrue(fso_.FolderExists(sFolder1)) + Call fso_.DeleteFolder(sFolder1) + + Call Dev_NewCase("Chain folders") + Dim sFolder2$: sFolder2 = sFolder1 & "\2" + Call EnsureFolderExists(sFolder2) + Call Dev_ExpectTrue(fso_.FolderExists(sFolder1)) + Call Dev_ExpectTrue(fso_.FolderExists(sFolder2)) + Call fso_.DeleteFolder(sFolder1) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_CreateTextFileUTF16() + On Error GoTo PROPAGATE_ERROR + + Call Dev_ExpectFalse(CreateTextFileUTF16(""), "Empty input") + + Call Dev_NewCase("Valid create file") + Dim sFile$: sFile = Dev_GetTestFolder & "\" & "test.txt" + Call Dev_ExpectTrue(CreateTextFileUTF16(sFile)) + Call Dev_ExpectTrue(fso_.FileExists(sFile), "Actually create the file") + Dim fs As Scripting.TextStream: Set fs = fso_.OpenTextFile(sFile, ForAppending, Format:=TristateTrue) + Call fs.WriteLine("test123") + Call fs.Close + + Call Dev_NewCase("Do not overwrite") + Call Dev_ExpectFalse(CreateTextFileUTF16(sFile, bOverwrite:=False)) + Set fs = fso_.OpenTextFile(sFile, ForReading, Format:=TristateTrue) + Dim sText$: sText = fs.ReadLine + Call fs.Close + Call Dev_ExpectEQ("test123", sText, "Do not overwrite file") + + Call Dev_NewCase("Overwrite") + Call Dev_ExpectTrue(CreateTextFileUTF16(sFile, bOverwrite:=True)) + Set fs = fso_.OpenTextFile(sFile, ForReading, Format:=TristateTrue) + Call Dev_ExpectTrue(fs.AtEndOfStream, "Overwrite contents") + Call fs.Close + + Call Dev_NewCase("Cannot overwrite") + Set fs = fso_.OpenTextFile(sFile, ForWriting) + Call Dev_ExpectFalse(CreateTextFileUTF16(sFile, bOverwrite:=True)) + Call fs.Close + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_AppendTextToFileUTF16() + On Error GoTo PROPAGATE_ERROR + + Dim sFile$: sFile = Dev_GetTestFolder & "\" & "test.txt" + Dim sMsg1$: sMsg1 = "testMsg1" + Dim sMsg2$: sMsg2 = "testMsg2" + + Call Dev_ExpectFalse(AppendTextToFileUTF16("", ""), "Empty input") + Call Dev_ExpectFalse(AppendTextToFileUTF16("", sMsg1), "Empty output file") + Call Dev_ExpectFalse(AppendTextToFileUTF16(sFile, sMsg1), "Non-existent output file") + + Call Dev_NewCase("Append text to empty file") + Call CreateTextFileUTF16(sFile) + Call Dev_ExpectTrue(AppendTextToFileUTF16(sFile, sMsg1), "Append text") + Dim fs As Scripting.TextStream: Set fs = fso_.OpenTextFile(sFile, ForReading, Format:=TristateTrue) + Dim sText$: sText = fs.ReadAll + Call fs.Close + Call Dev_ExpectEQ(sMsg1 & vbNewLine, sText, "Confirm message") + + Call Dev_NewCase("Append text to file") + Call Dev_ExpectTrue(AppendTextToFileUTF16(sFile, sMsg2), "Append text") + Set fs = fso_.OpenTextFile(sFile, ForReading, Format:=TristateTrue) + sText = fs.ReadAll + Call fs.Close + Call Dev_ExpectEQ(sMsg1 & vbNewLine & sMsg2 & vbNewLine, sText, "Confirm message") + + Call Dev_NewCase("Cannot append") + Set fs = fso_.OpenTextFile(sFile, ForAppending, Format:=TristateTrue) + Call Dev_ExpectFalse(AppendTextToFileUTF16(sFile, sMsg2)) + Call fs.Close + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_ReadTextFromFileUTF16() + On Error GoTo PROPAGATE_ERROR + + Dim sFile$: sFile = Dev_GetTestFolder & "\" & "test.txt" + Dim sMsg$: sMsg = "testMsg" + Dim sResult$: sResult = "test" + + Call Dev_NewCase("Empty input") + Call Dev_ExpectFalse(ReadTextFromFileUTF16("", sResult)) + Call Dev_ExpectEQ("test", sResult, "Do not overwrite output on fail") + + Call Dev_NewCase("Non-existent output file") + Call Dev_ExpectFalse(ReadTextFromFileUTF16(sFile, sResult)) + Call Dev_ExpectEQ("test", sResult, "Do not overwrite output on fail") + + Call CreateTextFileUTF16(sFile) + Dim fs As Scripting.TextStream: Set fs = fso_.OpenTextFile(sFile, ForAppending, Format:=TristateTrue) + Call fs.Write(sMsg) + Call fs.Close + + Call Dev_NewCase("Valid read text") + Call Dev_ExpectTrue(ReadTextFromFileUTF16(sFile, sResult)) + Call Dev_ExpectEQ(sMsg, sResult) + + Call Dev_NewCase("Read from occupied file") + Set fs = fso_.OpenTextFile(sFile, ForAppending, Format:=TristateTrue) + sResult = "" + Call Dev_ExpectTrue(ReadTextFromFileUTF16(sFile, sResult)) + Call Dev_ExpectEQ(sMsg, sResult) + Call fs.Close + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_CopyFileOrFolder() + On Error GoTo PROPAGATE_ERROR + + Call Dev_ExpectFalse(CopyFileOrFolder("", ""), "Empty input") + + Dim sFolder1$: sFolder1 = Dev_GetTestFolder & "\1" + Dim sFolder2$: sFolder2 = Dev_GetTestFolder & "\2" + Dim sFolder3$: sFolder3 = Dev_GetTestFolder & "\3" + Dim sFile1$: sFile1 = Dev_GetTestFolder & "\" & "test1.txt" + Dim sFile2$: sFile2 = Dev_GetTestFolder & "\" & "test2.txt" + + Call Dev_ExpectFalse(CopyFileOrFolder("", sFile1), "Empty source") + + Call fso_.CreateTextFile(sFile1).Close + Call Dev_ExpectFalse(CopyFileOrFolder(sFile1, ""), "Empty destination") + + Call fso_.CreateFolder(sFolder1) + Call Dev_ExpectFalse(CopyFileOrFolder(sFile1, sFolder1), "Copy file as folder") + + Call Dev_NewCase("Rename file") + Call Dev_ExpectTrue(CopyFileOrFolder(sFile1, sFile2)) + Call Dev_ExpectTrue(fso_.FileExists(sFile1), "Do not delete initial file") + Call Dev_ExpectTrue(fso_.FileExists(sFile2), "Create copy") + Call fso_.DeleteFile(sFile2) + + Call Dev_NewCase("Custom fso") + Call Dev_ExpectTrue(CopyFileOrFolder(sFile1, sFile2, fso_)) + Call Dev_ExpectTrue(fso_.FileExists(sFile1), "Do not delete initial file") + Call Dev_ExpectTrue(fso_.FileExists(sFile2), "Create copy") + + Call Dev_NewCase("Replace existing file") + Dim sMsg$: sMsg = "test1" + Call AppendTextToFileUTF16(sFile1, sMsg) + Call Dev_ExpectTrue(CopyFileOrFolder(sFile1, sFile2, fso_)) + Dim sResult$: Call ReadTextFromFileUTF16(sFile2, sResult) + Call Dev_ExpectEQ(sMsg & vbNewLine, sResult) + + Call Dev_NewCase("Copy file to new folder") + Dim sNewFile1$: sNewFile1 = sFolder1 & "\" & "test11.txt" + Call Dev_ExpectTrue(CopyFileOrFolder(sFile1, sNewFile1)) + Call Dev_ExpectTrue(fso_.FileExists(sFile1), "Do not delete initial file") + Call Dev_ExpectTrue(fso_.FileExists(sNewFile1), "Create copy") + + Call Dev_NewCase("Copy file to folder") + Call fso_.CreateFolder(sFolder2) + Dim sNewFile2$: sNewFile2 = sFolder2 & "\" & "test12.txt" + Call Dev_ExpectTrue(CopyFileOrFolder(sFile1, sNewFile2)) + Call Dev_ExpectTrue(fso_.FileExists(sFile1), "Do not delete initial file") + Call Dev_ExpectTrue(fso_.FileExists(sNewFile2), "Create copy") + + Call Dev_NewCase("Copy folder to new folder") + Call Dev_ExpectTrue(CopyFileOrFolder(sFolder1, sFolder3)) + Call Dev_ExpectTrue(fso_.FolderExists(sFolder1), "Do not delete initial folder") + Call Dev_ExpectTrue(fso_.FolderExists(sFolder3), "Create destination folder") + Call Dev_ExpectTrue(fso_.FileExists(sFolder3 & "\" & "test11.txt"), "Copy contained file") + + Call Dev_NewCase("Copy folder to folder") + Call Dev_ExpectTrue(CopyFileOrFolder(sFolder2, sFolder3)) + Call Dev_ExpectTrue(fso_.FolderExists(sFolder2), "Do not delete initial folder") + Call Dev_ExpectTrue(fso_.FileExists(sFolder3 & "\" & "test11.txt"), "Keep existing contents") + Call Dev_ExpectTrue(fso_.FileExists(sFolder3 & "\" & "test12.txt"), "Copy contained file") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_MoveFileOrFolder() + On Error GoTo PROPAGATE_ERROR + + Call Dev_ExpectFalse(MoveFileOrFolder("", ""), "Empty input") + + Dim sFolder1$: sFolder1 = Dev_GetTestFolder & "\1" + Dim sFolder2$: sFolder2 = Dev_GetTestFolder & "\2" + Dim sFolder3$: sFolder3 = Dev_GetTestFolder & "\3" + Dim sFile1$: sFile1 = Dev_GetTestFolder & "\" & "test1.txt" + Dim sFile2$: sFile2 = Dev_GetTestFolder & "\" & "test2.txt" + + Call Dev_ExpectFalse(MoveFileOrFolder("", sFile1), "Empty source") + + Call Dev_NewCase("Empty destination") + Call CreateTextFileUTF16(sFile1) + Call Dev_ExpectFalse(MoveFileOrFolder(sFile1, "")) + Call Dev_ExpectTrue(fso_.FileExists(sFile1), "Do not delete source if move failed") + + Call Dev_NewCase("Move file as folder") + Call fso_.CreateFolder(sFolder1) + Call Dev_ExpectFalse(MoveFileOrFolder(sFile1, sFolder1)) + Call Dev_ExpectTrue(fso_.FileExists(sFile1), "Do not delete source if move failed") + + Call Dev_NewCase("Move self") + Call Dev_ExpectFalse(MoveFileOrFolder(sFile1, sFile1)) + Call Dev_ExpectTrue(fso_.FileExists(sFile1), "Do not delete source if move failed") + + Call Dev_NewCase("Rename file") + Call Dev_ExpectTrue(MoveFileOrFolder(sFile1, sFile2)) + Call Dev_ExpectFalse(fso_.FileExists(sFile1), "Delete initial file") + Call Dev_ExpectTrue(fso_.FileExists(sFile2), "Renamed file exists") + + Call Dev_NewCase("Custom fso") + Call Dev_ExpectTrue(MoveFileOrFolder(sFile2, sFile1, fso_)) + Call Dev_ExpectFalse(fso_.FileExists(sFile2), "Delete initial file") + Call Dev_ExpectTrue(fso_.FileExists(sFile1), "Renamed file exists") + + Call Dev_NewCase("Replace existing file") + Call CreateTextFileUTF16(sFile2) + Dim sMsg$: sMsg = "test1" + Call AppendTextToFileUTF16(sFile2, sMsg) + Call Dev_ExpectTrue(MoveFileOrFolder(sFile2, sFile1, fso_)) + Call Dev_ExpectFalse(fso_.FileExists(sFile2), "Delete initial file") + Call Dev_ExpectTrue(fso_.FileExists(sFile1), "Renamed file exists") + Dim sResult$: Call ReadTextFromFileUTF16(sFile1, sResult) + Call Dev_ExpectEQ(sMsg & vbNewLine, sResult, "Replace content") + + Call Dev_NewCase("Move file to new folder") + Dim sNewFile1$: sNewFile1 = sFolder1 & "\" & "test11.txt" + Call Dev_ExpectTrue(MoveFileOrFolder(sFile1, sNewFile1)) + Call Dev_ExpectFalse(fso_.FileExists(sFile1), "Delete initial file") + Call Dev_ExpectTrue(fso_.FileExists(sNewFile1), "Moved file exists") + + Call Dev_NewCase("Move file to folder") + Call fso_.CreateFolder(sFolder2) + Dim sNewFile2$: sNewFile2 = sFolder2 & "\" & "test12.txt" + Call Dev_ExpectTrue(MoveFileOrFolder(sNewFile1, sNewFile2)) + Call Dev_ExpectFalse(fso_.FileExists(sNewFile1), "Delete initial file") + Call Dev_ExpectTrue(fso_.FileExists(sNewFile2), "Moved file exists") + + Call Dev_NewCase("Move folder to new folder") + Call Dev_ExpectTrue(MoveFileOrFolder(sFolder2, sFolder3)) + Call Dev_ExpectFalse(fso_.FolderExists(sFolder2), "Delete initial folder") + Call Dev_ExpectTrue(fso_.FolderExists(sFolder3), "Moved folder exists") + Call Dev_ExpectTrue(fso_.FileExists(sFolder3 & "\" & "test12.txt"), "Move contained file") + + Call Dev_NewCase("Move folder to folder") + Call CreateTextFileUTF16(sFile1) + Call Dev_ExpectTrue(MoveFileOrFolder(sFolder3, sFolder1)) + Call Dev_ExpectFalse(fso_.FolderExists(sFolder3), "Delete initial folder") + Call Dev_ExpectTrue(fso_.FileExists(sFile1), "Keep existing contents") + Call Dev_ExpectTrue(fso_.FileExists(sFolder1 & "\" & "test12.txt"), "Move contained file") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + diff --git a/src/test/s_Factorizator.cls b/src/test/s_Factorizator.cls new file mode 100644 index 0000000..555ccb3 --- /dev/null +++ b/src/test/s_Factorizator.cls @@ -0,0 +1,68 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "s_Factorizator" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +' ===== Test suite for Factorizator ======= +Option Explicit + +Private factors_ As CDS_Factorizator + +Public Function Setup() + ' Mandatory setup function + Set factors_ = New CDS_Factorizator +End Function + +Public Function Teardown() + ' Mandatory teardown function +End Function + +Public Function t_Access() + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Empty input") + Call factors_.Init(0) + Call Dev_ExpectEQ(CSet(), factors_.Values) + Call Dev_ExpectEQ(Empty, factors_.FactorFor(1337)) + Call Dev_ExpectEQ(0, factors_.FactorValueFor(1337)) + + Call Dev_NewCase("Access valid items") + Call Dev_AssertTrue(factors_.Insert(1337, 1)) + Call Dev_AssertFalse(factors_.Insert(1337, 3), "Duplicate inserts") + Call Dev_AssertTrue(factors_.Insert(42, 2)) + Dim iVals As New Scripting.Dictionary + iVals(1337) = 1 + iVals(42) = 2 + Call Dev_ExpectEQ(iVals, factors_.Values) + Call Dev_ExpectEQ(1337, factors_.FactorFor(1337)) + Call Dev_ExpectEQ(1, factors_.FactorValueFor(1337)) + Call Dev_ExpectEQ(42, factors_.FactorFor(42)) + Call Dev_ExpectEQ(2, factors_.FactorValueFor(42)) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_Factorization() + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Maximum gap") + Call factors_.Init(10) + Call factors_.Insert(1, 1) + Call factors_.Insert(2, 11) + Call Dev_ExpectEQ(2, factors_.FactorFor(1)) + Call Dev_ExpectEQ(2, factors_.FactorFor(2)) + + Call Dev_NewCase("Minimal increment") + Call factors_.Insert(3, 11 + 0.001) + Call Dev_ExpectEQ(3, factors_.FactorFor(3)) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function diff --git a/src/test/s_Graph.cls b/src/test/s_Graph.cls new file mode 100644 index 0000000..bf916bf --- /dev/null +++ b/src/test/s_Graph.cls @@ -0,0 +1,414 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "s_Graph" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +' ===== Test suite for Graph data structure ======= +Option Explicit + +Private graph_ As CDS_Graph + +Public Function Setup() + ' Mandatory setup function + Set graph_ = New CDS_Graph +End Function + +Public Function Teardown() + ' Mandatory teardown function +End Function + +Public Function t_AddNode() + On Error GoTo PROPAGATE_ERROR + + Call graph_.AddNode(1337) + Call Dev_ExpectEQ(1, graph_.Size, "Valid add") + Call Dev_ExpectTrue(graph_.HasNode(1337)) + Call Dev_ExpectFalse(graph_.HasNode("1337"), "Do not use type conversion for node comparisons") + + Call graph_.AddNode(1337) + Call Dev_ExpectEQ(1, graph_.Size, "Duplicate add") + + Call Dev_NewCase("Different node data types") + Call graph_.AddNode("abc") + Call Dev_ExpectEQ(2, graph_.Size) + Call Dev_ExpectEQ(CColl(1337, "abc"), graph_.Nodes) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_AddEdge() + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Valid edge") + Call graph_.AddNode(1) + Call graph_.AddNode(2) + Dim iEdge As CDS_Edge: Set iEdge = graph_.AddEdge(1, 2) + Call Dev_AssertNotNothing(iEdge) + Call Dev_ExpectTrue(graph_.HasEdge(1, 2)) + Call Dev_ExpectEQ(1, iEdge.source_) + Call Dev_ExpectEQ(2, iEdge.dest_) + + Call Dev_NewCase("Duplicate edge") + Call Dev_ExpectNothing(graph_.AddEdge(1, 2)) + + Call Dev_NewCase("Self edge") + Set iEdge = graph_.AddEdge(1, 1) + Call Dev_AssertNotNothing(iEdge) + Call Dev_ExpectTrue(graph_.HasEdge(1, 1)) + Call Dev_ExpectEQ(1, iEdge.source_) + Call Dev_ExpectEQ(1, iEdge.dest_) + + Call Dev_NewCase("Add missing nodes") + Set iEdge = graph_.AddEdge(3, 4) + Call Dev_AssertNotNothing(iEdge) + Call Dev_ExpectTrue(graph_.HasEdge(3, 4)) + Call Dev_ExpectEQ(3, iEdge.source_) + Call Dev_ExpectEQ(4, iEdge.dest_) + Call Dev_ExpectEQ(4, graph_.Size, "Check node count") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_Clear() + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Empty graph") + On Error Resume Next + Call graph_.Clear + Call Dev_ExpectNoError + On Error GoTo PROPAGATE_ERROR + Call Dev_ExpectEQ(0, graph_.Size) + + Call Dev_NewCase("Valid graph") + Call graph_.AddNode(1) + Call graph_.AddEdge(2, 3) + Call Dev_ExpectEQ(3, graph_.Size) + Call Dev_ExpectTrue(graph_.HasEdge(2, 3)) + Call graph_.Clear + Call Dev_ExpectEQ(0, graph_.Size) + Call Dev_ExpectFalse(graph_.HasEdge(2, 3)) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_FilterInternalEdges() + On Error GoTo PROPAGATE_ERROR + + Dim iEdges As New Collection + Call Dev_ExpectEQ(iEdges, graph_.FilterInternalEdges(CSet()), "Empty graph") + + Call Dev_NewCase("No edges") + Call graph_.AddNode(1): Call graph_.AddNode(2) + Call Dev_ExpectEQ(iEdges, graph_.FilterInternalEdges(CSet()), "Empty filter") + Call Dev_ExpectEQ(iEdges, graph_.FilterInternalEdges(CSet(3, 4)), "Invalid nodes") + Call Dev_ExpectEQ(iEdges, graph_.FilterInternalEdges(CSet(1, 2)), "No edges") + + Call Dev_NewCase("Valid filter") + Dim edge1 As CDS_Edge: Set edge1 = graph_.AddEdge(1, 2) + Dim edge2 As CDS_Edge: Set edge2 = graph_.AddEdge(1, 3) + Call Dev_ExpectEQ(iEdges, graph_.FilterInternalEdges(CSet(2)), "Filter end") + Call Dev_ExpectEQ(iEdges, graph_.FilterInternalEdges(CSet(1)), "Filter start") + Call iEdges.Add(edge1.Clone) + Call Dev_ExpectEQ(iEdges, graph_.FilterInternalEdges(CSet(1, 2)), "Filter internal") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_StrongComponents() + On Error GoTo PROPAGATE_ERROR + + Dim iProcessor As New API_StrongComponents + Dim iComponents As New Collection + Call Dev_ExpectEQ(iComponents, iProcessor.GetComponents(graph_), "Empty graph") + + Call Dev_NewCase("No edges") + Call graph_.AddNode(1): Call graph_.AddNode(2) + Call Dev_ExpectEQ(iComponents, iProcessor.GetComponents(graph_)) + + Call Dev_NewCase("Self edge") + Set iComponents = CColl(CSet(1)) + Call graph_.AddEdge(1, 1) + Call Dev_ExpectEQ(iComponents, iProcessor.GetComponents(graph_)) + + Call Dev_NewCase("Valid strong components") + Set iComponents = CColl(CSet(2, 3, 4), CSet(1)) + Call graph_.AddEdge(1, 2) + Call graph_.AddEdge(2, 3) + Call graph_.AddEdge(3, 2) + Call graph_.AddEdge(2, 4) + Call graph_.AddEdge(4, 2) + Call Dev_ExpectEQ(iComponents, iProcessor.GetComponents(graph_)) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_LinkedComponents() + On Error GoTo PROPAGATE_ERROR + + Dim iProcessor As New API_LinkedComponents + + Call Dev_NewCase("Empty graph") + Dim iComponents As New Scripting.Dictionary + Call Dev_ExpectEQ(iComponents, iProcessor.GetComponents(graph_)) + Call Dev_ExpectEQ(0, iProcessor.CountComponents) + + Call Dev_NewCase("No edges") + Call graph_.AddNode(1): Call graph_.AddNode(2) + iComponents(1) = 0 + iComponents(2) = 1 + Call Dev_ExpectEQ(iComponents, iProcessor.GetComponents(graph_)) + Call Dev_ExpectEQ(2, iProcessor.CountComponents) + + Call Dev_NewCase("Simple component - straight") + Call graph_.AddEdge(1, 2) + iComponents(1) = 0 + iComponents(2) = 0 + Call Dev_ExpectEQ(iComponents, iProcessor.GetComponents(graph_)) + Call Dev_ExpectEQ(1, iProcessor.CountComponents) + + Call Dev_NewCase("Simple component - reversed") + Set graph_ = New CDS_Graph + Call graph_.AddEdge(2, 1) + iComponents(1) = 0 + iComponents(2) = 0 + Call Dev_ExpectEQ(iComponents, iProcessor.GetComponents(graph_)) + Call Dev_ExpectEQ(1, iProcessor.CountComponents) + + Call Dev_NewCase("Multiple components") + Call graph_.AddEdge(3, 4) + Call graph_.AddEdge(4, 3) + Call graph_.AddEdge(4, 5) + Call graph_.AddNode(6) + iComponents(1) = 0 + iComponents(2) = 0 + iComponents(3) = 1 + iComponents(4) = 1 + iComponents(5) = 1 + iComponents(6) = 2 + Call Dev_ExpectEQ(iComponents, iProcessor.GetComponents(graph_)) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_Expansions() + On Error GoTo PROPAGATE_ERROR + + Dim iEmpty As New Scripting.Dictionary + Dim iTest As New Scripting.Dictionary + + Call Dev_NewCase("Empty graph") + Call graph_.ExpandOutsOnce(iTest) + Call Dev_ExpectEQ(iEmpty, iTest, "Outs once") + Call graph_.ExpandInsOnce(iTest) + Call Dev_ExpectEQ(iEmpty, iTest, "Ins once") + Call graph_.ExpandBiderctionalOnce(iTest) + Call Dev_ExpectEQ(iEmpty, iTest, "Both once") + Call graph_.ExpandOutputs(iTest) + Call Dev_ExpectEQ(iEmpty, iTest, "Outs all") + Call graph_.ExpandInputs(iTest) + Call Dev_ExpectEQ(iEmpty, iTest, "Ins all") + Call graph_.ExpandBiderctional(iTest) + Call Dev_ExpectEQ(iEmpty, iTest, "Both all") + + Call Dev_NewCase("Empty input") + Call graph_.AddEdge(1, 2): Call graph_.AddEdge(3, 1) + Call graph_.AddEdge(3, 4): Call graph_.AddEdge(5, 6): Call graph_.AddEdge(5, 8) + Call graph_.AddEdge(6, 7): Call graph_.AddEdge(7, 6): Call graph_.AddEdge(9, 3) + Call graph_.ExpandOutsOnce(iTest) + Call Dev_ExpectEQ(iEmpty, iTest, "Outs once") + Call graph_.ExpandInsOnce(iTest) + Call Dev_ExpectEQ(iEmpty, iTest, "Ins once") + Call graph_.ExpandBiderctionalOnce(iTest) + Call Dev_ExpectEQ(iEmpty, iTest, "Both once") + Call graph_.ExpandOutputs(iTest) + Call Dev_ExpectEQ(iEmpty, iTest, "Outs all") + Call graph_.ExpandInputs(iTest) + Call Dev_ExpectEQ(iEmpty, iTest, "Ins all") + Call graph_.ExpandBiderctional(iTest) + Call Dev_ExpectEQ(iEmpty, iTest, "Both all") + + Call Dev_NewCase("Valid expansion") + Set iTest = CSet(1, 5) + Call graph_.ExpandOutsOnce(iTest) + Call Dev_ExpectEQ(CSet(1, 5, 2, 8, 6), iTest, "Outs once") + Set iTest = CSet(1, 5) + Call graph_.ExpandInsOnce(iTest) + Call Dev_ExpectEQ(CSet(1, 5, 3), iTest, "Ins once") + Set iTest = CSet(1, 5) + Call graph_.ExpandBiderctionalOnce(iTest) + Call Dev_ExpectEQ(CSet(1, 5, 2, 8, 6, 3), iTest, "Both once") + Set iTest = CSet(1, 5) + Call graph_.ExpandOutputs(iTest) + Call Dev_ExpectEQ(CSet(1, 5, 2, 8, 6, 7), iTest, "Outs all") + Set iTest = CSet(1, 5) + Call graph_.ExpandInputs(iTest) + Call Dev_ExpectEQ(CSet(1, 5, 3, 9), iTest, "Ins all") + Set iTest = CSet(1, 5) + Call graph_.ExpandBiderctional(iTest) + Call Dev_ExpectEQ(CSet(1, 5, 2, 3, 4, 6, 7, 8, 9), iTest, "Both all") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_Ordering() + On Error GoTo PROPAGATE_ERROR + + Dim iProcessor As New API_GraphOrdering: Call iProcessor.Init(graph_) + Dim iEmpty As New Collection + + Call Dev_NewCase("Empty graph") + Call Dev_ExpectEQ(iEmpty, iProcessor.TopologicalOrder) + Call Dev_ExpectEQ(iEmpty, iProcessor.ReverseTopologicalOrder) + Call Dev_ExpectEQ(iEmpty, iProcessor.TopologicalComponentOrder) + Call Dev_ExpectEQ(iEmpty, iProcessor.ReverseTopologicalComponentOrder) + + Call Dev_NewCase("Empty input") + Call graph_.AddEdge(1, 2): Call graph_.AddEdge(3, 1) + Call graph_.AddEdge(3, 4): Call graph_.AddEdge(5, 6): Call graph_.AddEdge(5, 8) + Call graph_.AddEdge(6, 7): Call graph_.AddEdge(7, 6): Call graph_.AddEdge(9, 3) + Call iProcessor.Init(graph_) + + Call Dev_NewCase("Valid ordering") + Call Dev_ExpectTrue(TestTopological(iProcessor.TopologicalOrder)) + Call Dev_ExpectTrue(TestTopological(RevertCollection(iProcessor.ReverseTopologicalOrder))) + Call Dev_ExpectTrue(TestComponent(iProcessor.TopologicalComponentOrder)) + Call Dev_ExpectTrue(TestComponent(RevertCollection(iProcessor.ReverseTopologicalComponentOrder))) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_SortLayers() + On Error GoTo PROPAGATE_ERROR + + Dim iProcessor As New API_GraphOrdering: Call iProcessor.Init(graph_) + Dim iEmpty As New Collection + + Call Dev_ExpectEQ(iEmpty, iProcessor.SortLayers(iEmpty), "Empty graph") + + Call graph_.AddEdge(1, 2): Call graph_.AddEdge(3, 1) + Call graph_.AddEdge(3, 4): Call graph_.AddEdge(5, 6): Call graph_.AddEdge(5, 8) + Call graph_.AddEdge(6, 7): Call graph_.AddEdge(7, 6): Call graph_.AddEdge(9, 3) + Call iProcessor.Init(graph_) + + Call Dev_ExpectEQ(iEmpty, iProcessor.SortLayers(iEmpty), "Empty input") + Call Dev_ExpectEQ(iEmpty, iProcessor.SortLayers(CColl(1337, 42)), "Ignore invalid IDs") + + Call Dev_NewCase("Sorting") + Call Dev_ExpectEQ(CColl(5, 6, 8, 7, 9, 3, 1, 4, 2), iProcessor.SortLayers(CColl(1, 2, 3, 4, 5, 6, 7, 8, 9)), "Full order") + Call Dev_ExpectEQ(CColl(9, 3, 4, 1, 2, 5, 8, 6, 7), iProcessor.SortLayers(CColl(9, 8, 7, 6, 5, 4, 3, 2, 1)), "Reversed order") + Call Dev_ExpectEQ(CColl(5, 6, 8, 7, 9, 3, 1, 4, 2), iProcessor.SortLayers(CColl(1, 2, 3, 4, 5, 6, 7, 8, 9, 1337, 42)), "Mixed invalid") + Call Dev_ExpectEQ(CColl(5, 7, 9, 3, 1), iProcessor.SortLayers(CColl(1, 3, 5, 7, 9)), "Partial order") + Call Dev_ExpectEQ(CColl(9, 1), iProcessor.SortLayers(CColl(1, 9)), "Missing component") + + Call Dev_NewCase("Sibling sorting") + Call graph_.Clear + Call graph_.AddEdge(1, 3): Call graph_.AddEdge(2, 3) + Call graph_.AddEdge(4, 5): Call graph_.AddEdge(3, 5): Call graph_.AddEdge(3, 6) + Call iProcessor.Init(graph_) + Call Dev_ExpectEQ(CColl(1, 2, 4, 3, 5, 6), iProcessor.SortLayers(CColl(1, 2, 3, 4, 5, 6))) + Call graph_.AddEdge(6, 5) + Call iProcessor.Init(graph_) + Call Dev_ExpectEQ(CColl(1, 2, 4, 3, 6, 5), iProcessor.SortLayers(CColl(1, 2, 3, 4, 5, 6))) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_SortDeep() + On Error GoTo PROPAGATE_ERROR + + Dim iProcessor As New API_GraphOrdering: Call iProcessor.Init(graph_) + Dim iEmpty As New Collection + + Call Dev_ExpectEQ(iEmpty, iProcessor.SortDeep(iEmpty), "Empty graph") + + Call graph_.AddEdge(1, 2): Call graph_.AddEdge(3, 1) + Call graph_.AddEdge(3, 4): Call graph_.AddEdge(5, 6): Call graph_.AddEdge(5, 8) + Call graph_.AddEdge(6, 7): Call graph_.AddEdge(7, 6): Call graph_.AddEdge(9, 3) + Call iProcessor.Init(graph_) + + Call Dev_ExpectEQ(iEmpty, iProcessor.SortDeep(iEmpty), "Empty input") + Call Dev_ExpectEQ(iEmpty, iProcessor.SortDeep(CColl(1337, 42)), "Ignore invalid IDs") + + Call Dev_NewCase("Sorting") + Call Dev_ExpectEQ(CColl(5, 6, 7, 8, 9, 3, 1, 2, 4), iProcessor.SortDeep(CColl(1, 2, 3, 4, 5, 6, 7, 8, 9)), "Full order") + Call Dev_ExpectEQ(CColl(9, 3, 4, 1, 2, 5, 8, 6, 7), iProcessor.SortDeep(CColl(9, 8, 7, 6, 5, 4, 3, 2, 1)), "Reversed order") + Call Dev_ExpectEQ(CColl(5, 6, 7, 8, 9, 3, 1, 2, 4), iProcessor.SortDeep(CColl(1, 2, 3, 4, 5, 6, 7, 8, 9, 1337, 42)), "Mixed invalid") + Call Dev_ExpectEQ(CColl(5, 7, 9, 3, 1), iProcessor.SortDeep(CColl(1, 3, 5, 7, 9)), "Partial order") + Call Dev_ExpectEQ(CColl(9, 1), iProcessor.SortDeep(CColl(1, 9)), "Missing component") + + Call Dev_NewCase("Sibling sorting") + Call graph_.Clear + Call graph_.AddEdge(1, 3): Call graph_.AddEdge(2, 3) + Call graph_.AddEdge(4, 5): Call graph_.AddEdge(3, 5): Call graph_.AddEdge(3, 6) + Call iProcessor.Init(graph_) + Call Dev_ExpectEQ(CColl(1, 3, 5, 6, 2, 4), iProcessor.SortDeep(CColl(1, 2, 3, 4, 5, 6))) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +' ====== +Private Function TestTopological(iOrder As Collection) As Boolean + TestTopological = False + + Dim nItem1& + Dim nItem2& + For nItem1 = iOrder.Count To 2 Step -1 + For nItem2 = 1 To nItem1 - 1 Step 1 + If graph_.HasEdge(iOrder(nItem1), iOrder(nItem2)) Then _ + If Not graph_.HasEdge(iOrder(nItem2), iOrder(nItem1)) Then _ + Exit Function + Next nItem2 + Next nItem1 + + TestTopological = True +End Function + +Private Function TestComponent(iOrder As Collection) As Boolean + TestComponent = False + If Not TestTopological(iOrder) Then _ + Exit Function + + Dim iProcessor As New API_LinkedComponents + Dim iComponents As Scripting.Dictionary: Set iComponents = iProcessor.GetComponents(graph_) + + Dim iVisited As New Scripting.Dictionary + Dim nLast&: nLast = -1 + Dim nComponent& + Dim vNode As Variant + For Each vNode In iOrder + nComponent = iComponents(vNode) + If nLast <> nComponent Then + If iVisited.Exists(nComponent) Then _ + Exit Function + Call iVisited.Add(nComponent, 0) + nLast = nComponent + End If + Next vNode + + TestComponent = True +End Function + diff --git a/src/test/s_JSON.cls b/src/test/s_JSON.cls new file mode 100644 index 0000000..585cd6b --- /dev/null +++ b/src/test/s_JSON.cls @@ -0,0 +1,212 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "s_JSON" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Private json_ As API_JSON + +Public Function Setup() + ' Mandatory setup function + Set json_ = New API_JSON +End Function + +Public Function Teardown() + ' Mandatory teardown function +End Function + +Public Function t_GeneratePlain() + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Invalid object") + On Error Resume Next + Call json_.CreateJSON(json_) + Call Dev_ExpectAnyError + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Basic types") + Call Dev_ExpectEQ("""""", json_.CreateJSON("")) + Call Dev_ExpectEQ(json_.CreateJSON(0), "0") + Call Dev_ExpectEQ(json_.CreateJSON(-1), "-1") + Call Dev_ExpectEQ(json_.CreateJSON(1.234), "1.234") + + Call Dev_NewCase("String") + Call Dev_ExpectEQ(json_.CreateJSON("test"), """test""") + Call Dev_ExpectEQ(json_.CreateJSON(""), """""", "Cyrillic") + Call Dev_ExpectEQ(json_.CreateJSON("1 "" 2"), """1 \"" 2""", "Escape quote") + Call Dev_ExpectEQ(json_.CreateJSON("test1" & vbNewLine & "test2"), """test1\r\ntest2""", "Escape special symbol") + + Call Dev_NewCase("Array 1D") + Call Dev_ExpectEQ(json_.CreateJSON(Array()), "[]") + Call Dev_ExpectEQ(json_.CreateJSON(Array(1, 2, Array())), "[1,2,[]]") + Call Dev_ExpectEQ(json_.CreateJSON(Array(Array(1, 2), 3)), "[[1,2],3]") + + Call Dev_NewCase("Array 2D") + Dim testArr(1 To 2, 1 To 2) As Variant + testArr(1, 1) = 1337 + testArr(2, 2) = 42 + Call Dev_ExpectEQ(json_.CreateJSON(testArr), "[[1337,null],[null,42]]") + + Call Dev_NewCase("Collection") + Call Dev_ExpectEQ(json_.CreateJSON(CColl()), "[]") + Call Dev_ExpectEQ(json_.CreateJSON(CColl(1, 2, 3)), "[1,2,3]") + Call Dev_ExpectEQ(json_.CreateJSON(CColl(1, CColl(2, 3))), "[1,[2,3]]") + + Call Dev_NewCase("Dictionary") + Dim testDict As New Scripting.Dictionary + Call Dev_ExpectEQ(json_.CreateJSON(testDict), "{}") + testDict.Item(1) = 42 + testDict.Item("test") = 1337 + Set testDict.Item(2) = CColl(1, 2) + Call Dev_ExpectEQ(json_.CreateJSON(testDict), "{""1"":42,""test"":1337,""2"":[1,2]}") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_GenerateMods() + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Escape unicode") + json_.escapeUnicode_ = True + Call Dev_ExpectEQ(json_.CreateJSON(""), """\u0442\u0435\u0441\u0442""") + json_.escapeUnicode_ = False + Call Dev_ExpectEQ(json_.CreateJSON(""), """""") + + Call Dev_NewCase("Escape slash") + json_.escapeSlash_ = True + Call Dev_ExpectEQ(json_.CreateJSON("/"), """\/""") + json_.escapeSlash_ = False + Call Dev_ExpectEQ(json_.CreateJSON("/"), """/""") + + Call Dev_NewCase("Pretty print") + Dim test1$: test1 = _ + "[" & vbNewLine & _ + " 1," & vbNewLine & _ + " [" & vbNewLine & _ + " 2," & vbNewLine & _ + " 3" & vbNewLine & _ + " ]" & vbNewLine & _ + "]" + Call json_.SetupMultiline(2) + Call Dev_ExpectEQ(json_.CreateJSON(Array(1, Array(2, 3))), test1, "Print2") + + Dim test2$: test2 = _ + "[" & vbNewLine & _ + " 1," & vbNewLine & _ + " [" & vbNewLine & _ + " 2," & vbNewLine & _ + " 3" & vbNewLine & _ + " ]" & vbNewLine & _ + "]" + Call json_.SetupMultiline(4) + Call Dev_ExpectEQ(json_.CreateJSON(Array(1, Array(2, 3))), test2, "Print4") + + Dim test3$: test3 = _ + "[" & vbNewLine & _ + "-1," & vbNewLine & _ + "-[" & vbNewLine & _ + "--2," & vbNewLine & _ + "--3" & vbNewLine & _ + "-]" & vbNewLine & _ + "]" + Call json_.SetupMultiline("-") + Call Dev_ExpectEQ(json_.CreateJSON(Array(1, Array(2, 3))), test3, "Print-") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_ParseValid() + On Error GoTo PROPAGATE_ERROR + + Dim testDict As Scripting.Dictionary + + Call Dev_NewCase("Empty") + Call Dev_ExpectEQ(json_.Parse("[]"), CColl()) + Call Dev_ExpectEQ(json_.Parse("{}"), CSet()) + + Call Dev_NewCase("Basic types") + Call Dev_ExpectEQ(json_.Parse("[1,2,""3""]"), CColl(1, 2, "3")) + Call Dev_ExpectEQ(json_.Parse("[1,2,null]"), CColl(1, 2, Null)) + Set testDict = New Scripting.Dictionary + testDict.Item("key1") = 1.234 + testDict.Item("key2") = 0 + Call Dev_ExpectEQ(json_.Parse("{""key1"":1.234,""key2"":0}"), testDict) + + Call Dev_NewCase("Compound structures") + Call Dev_ExpectEQ(json_.Parse("[1,[2,3],{}]"), CColl(1, CColl(2, 3), CSet())) + Set testDict = New Scripting.Dictionary + Set testDict.Item("key1") = CColl(1, 2) + Set testDict.Item("key2") = CColl(42) + Call Dev_ExpectEQ(json_.Parse("{""key1"":[1,2],""key2"":[42]}"), testDict) + + Call Dev_NewCase("Slashes") + + + Call Dev_NewCase("Pretty printed") + Call json_.SetupMultiline(2) + Dim sInput$: sInput = json_.CreateJSON(Array(1, Array(2, 3))) + Call Dev_ExpectEQ(json_.Parse(sInput), CColl(1, CColl(2, 3))) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_ParsePath() + On Error GoTo PROPAGATE_ERROR + + Dim testDict As Scripting.Dictionary + + json_.escapeSlash_ = False + Set testDict = New Scripting.Dictionary + testDict.Item("key1") = "C:ToolsPython39-venvScriptspython.exe" + Call Dev_ExpectEQ(json_.Parse("{""key1"": ""C:\Tools\Python39-venv\Scripts\python.exe""}"), testDict) + testDict.Item("key1") = "C:\Tools\Python39-venv\Scripts\python.exe" + Call Dev_ExpectEQ(json_.Parse("{""key1"": ""C:\\Tools\\Python39-venv\\Scripts\\python.exe""}"), testDict) + testDict.Item("key1") = "C:/Tools/Python39-venv/Scripts/python.exe" + Call Dev_ExpectEQ(json_.Parse("{""key1"": ""C:/Tools/Python39-venv/Scripts/python.exe""}"), testDict) + testDict.Item("key1") = "C:/Tools/Python39-venv/Scripts/python.exe" + Call Dev_ExpectEQ(json_.Parse("{""key1"": ""C:\/Tools\/Python39-venv\/Scripts\/python.exe""}"), testDict) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_ParseInvalid() + On Error GoTo PROPAGATE_ERROR + + Call ExpectParserError("") + Call ExpectParserError("]") + Call ExpectParserError("}") + Call ExpectParserError("{") + Call ExpectParserError("[") + Call ExpectParserError("[[]") + Call ExpectParserError("[]]") + Call ExpectParserError("{{}") + Call ExpectParserError("{}}") + Call ExpectParserError("abc") + Call ExpectParserError("{key:1}") + Call ExpectParserError("[{]}") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +' ====== +Private Function ExpectParserError(sInput$, Optional sClause$ = "") + On Error Resume Next + Call json_.Parse(sInput) + Call Dev_ExpectAnyError(sClause & vbNewLine & "Input: " & sInput) + On Error GoTo 0 +End Function diff --git a/src/test/s_Logger.cls b/src/test/s_Logger.cls new file mode 100644 index 0000000..a69f15f --- /dev/null +++ b/src/test/s_Logger.cls @@ -0,0 +1,119 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "s_Logger" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Private fso_ As Scripting.FileSystemObject +Private logFile_ As String +Private log_ As API_Logger + +Private Const TEST_LOG_NAME = "testlog.log" + +Public Function Setup() + ' Mandatory setup function + Set fso_ = New Scripting.FileSystemObject + Call Teardown + + logFile_ = ThisWorkbook.Path & "\" & TEST_LOG_NAME + Set log_ = New API_Logger + Call log_.Init(logFile_) +End Function + +Public Function Teardown() + ' Mandatory teardown function + If fso_.FileExists(logFile_) Then _ + Call fso_.DeleteFile(logFile_) +End Function + +Public Function t_Init() + On Error GoTo PROPAGATE_ERROR + + Dim newLog As New API_Logger + + Call Dev_NewCase("Empty Init") + On Error Resume Next + Call newLog.Init("") + Call Dev_ExpectNoError + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("File Init") + Dim sFile1$: sFile1 = logFile_ & "1" + Call newLog.Init(sFile1) + + Call Dev_ExpectTrue(fso_.FileExists(logFile_)) + Call Dev_ExpectEQ(sFile1, newLog.LogFileName) + Dim cLines As Collection: Set cLines = ReadLogLines(sFile1) + Call Dev_AssertEQ(1, cLines.Count) + Call Dev_ExpectLike(cLines(1), "*Log file created") + + Call Dev_NewCase("Reinit") + Dim sFile2$: sFile2 = logFile_ & "2" + Call newLog.Init(sFile2) + Call Dev_ExpectTrue(fso_.FileExists(logFile_)) + Call Dev_ExpectTrue(fso_.FileExists(sFile2)) + + Call fso_.DeleteFile(sFile1) + Call fso_.DeleteFile(sFile2) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_Timestamp() + On Error GoTo PROPAGATE_ERROR + + Dim cLines As Collection: Set cLines = ReadLogLines(logFile_) + Call Dev_AssertEQ(1, cLines.Count) + Dim dTime As Double: dTime = ExtractTimestamp(cLines(1)) + Call Dev_ExpectAEQ(Now(), dTime, 0, "Valid stamp") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_Log() + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Valid messaging") + Call log_.Log("") + Call log_.Log("test") + Call log_.Log("Multiline: line1" & vbNewLine & "Multiline: line2") + Dim cLines As Collection: Set cLines = ReadLogLines(logFile_) + Call Dev_AssertEQ(5, cLines.Count) + Call Dev_ExpectEQ("Log file created", TrimTimestamp(cLines(1))) + Call Dev_ExpectEQ("", TrimTimestamp(cLines(2))) + Call Dev_ExpectEQ("test", TrimTimestamp(cLines(3))) + Call Dev_ExpectEQ("Multiline: line1", TrimTimestamp(cLines(4))) + Call Dev_ExpectEQ("Multiline: line2", TrimTimestamp(cLines(5))) + + Call Dev_NewCase("Try logging when log was deleted") + Call fso_.DeleteFile(logFile_) + On Error Resume Next + Call log_.Log("test") + Call Dev_ExpectNoError + Call Dev_ExpectFalse(fso_.FileExists(logFile_)) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +' ===== +Private Function ReadLogLines(sFile$) As Collection + Const UTF16_MODE = TristateTrue + Set ReadLogLines = New Collection + Dim fso As New Scripting.FileSystemObject + Dim fs As Scripting.TextStream: Set fs = fso.OpenTextFile(sFile, ForReading, Format:=UTF16_MODE) + Do While Not fs.AtEndOfStream + Call ReadLogLines.Add(fs.ReadLine) + Loop + Call fs.Close +End Function diff --git a/src/test/s_ParseDate.cls b/src/test/s_ParseDate.cls new file mode 100644 index 0000000..677ace7 --- /dev/null +++ b/src/test/s_ParseDate.cls @@ -0,0 +1,160 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "s_ParseDate" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +' ===== Test suite for date Parsing ======= +Option Explicit + +Private parser_ As ParserDate + +Public Function Setup() + ' Mandatory setup function + Set parser_ = New ParserDate +End Function + +Public Function Teardown() + ' Mandatory teardown function +End Function + +Public Function t_DefaultState() + On Error GoTo PROPAGATE_ERROR + + Call Dev_ExpectEQ(0, parser_.day_) + Call Dev_ExpectEQ(0, parser_.month_) + Call Dev_ExpectEQ(0, parser_.year_) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_Init() + On Error GoTo PROPAGATE_ERROR + + Call parser_.Init(12, 1, 20) + Call Dev_NewCase("Init real date") + Call Dev_ExpectEQ(12, parser_.day_) + Call Dev_ExpectEQ(1, parser_.month_) + Call Dev_ExpectEQ(20, parser_.year_) + + Call Dev_NewCase("Init invalid date") + Call parser_.Init(99, 99, 20) + Call Dev_ExpectEQ(99, parser_.day_) + Call Dev_ExpectEQ(99, parser_.month_) + Call Dev_ExpectEQ(20, parser_.year_) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_Date() + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Empty") + Call Dev_ExpectFalse(parser_.IsValidDate) + Call Dev_ExpectEQ(0, parser_.DDate) + + Call Dev_NewCase("Valid date value") + Call parser_.Init(31, 1, 20) + Call Dev_ExpectTrue(parser_.IsValidDate) + Call Dev_ExpectEQ(43861#, parser_.DDate) + + Call Dev_NewCase("Invalid date value") + Call parser_.Init(32, 1, 20) + Call Dev_ExpectFalse(parser_.IsValidDate) + Call Dev_ExpectEQ(0#, parser_.DDate) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_AsString() + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Empty") + Call Dev_ExpectEQ("00.00.00", parser_.AsDigitsString) + Call Dev_ExpectEQ("INVALID_DATE", parser_.AsTextString) + + Call Dev_NewCase("Short year") + Call parser_.Init(31, 1, 20) + Call Dev_ExpectEQ("31.01.20", parser_.AsDigitsString) + Call Dev_ExpectEQ("31 2020 ", parser_.AsTextString) + + Call Dev_NewCase("Long year") + Call parser_.Init(31, 1, 1989) + Call Dev_ExpectEQ("31.01.1989", parser_.AsDigitsString) + Call Dev_ExpectEQ("31 1989 ", parser_.AsTextString) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_Test() + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Empty string") + Call Dev_ExpectFalse(parser_.Test("")) + + Call Dev_NewCase("Invalid inputs") + Call Dev_ExpectFalse(parser_.Test("123")) + Call Dev_ExpectFalse(parser_.Test("12 2000 ")) + + Call Dev_NewCase("Incomplete inputs") + Call Dev_ExpectFalse(parser_.Test("12 20")) + Call Dev_ExpectFalse(parser_.Test(" 2000 ")) + Call Dev_ExpectFalse(parser_.Test("13 2000 ")) + Call Dev_ExpectFalse(parser_.Test("13 00 ")) + + Call Dev_ExpectTrue(parser_.Test("13.01.2000"), "Valid input: 13.01.2000") + Call Dev_ExpectTrue(parser_.Test("13.01.2000 ."), "Valid input: 13.01.2000 .") + Call Dev_ExpectTrue(parser_.Test("13.01.00"), "Valid input: 13.01.00") + Call Dev_ExpectTrue(parser_.Test("13 2000 "), "Valid input: 13 2000 ") + Call Dev_ExpectTrue(parser_.Test("13 2000 "), "Valid input: 13 2000 ") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_Parse() + On Error GoTo PROPAGATE_ERROR + + Call Dev_ExpectFalse(parser_.Parse(""), "Parse empty string") + Call Dev_ExpectFalse(parser_.Parse("123"), "Parse invalid") + + Call Dev_NewCase("Parse 13.01.2000") + Call Dev_AssertTrue(parser_.Parse("13.01.2000")) + Call Dev_ExpectEQ(13, parser_.day_) + Call Dev_ExpectEQ(1, parser_.month_) + Call Dev_ExpectEQ(2000, parser_.year_) + + Call Dev_NewCase("Do not reset after fail?") + Call Dev_AssertFalse(parser_.Parse("123")) + Call Dev_ExpectEQ(13, parser_.day_) + Call Dev_ExpectEQ(1, parser_.month_) + Call Dev_ExpectEQ(2000, parser_.year_) + + Call Dev_NewCase("Parse 13 2000 ") + Call Dev_AssertTrue(parser_.Parse("13 2000 ")) + Call Dev_ExpectEQ(13, parser_.day_) + Call Dev_ExpectEQ(1, parser_.month_) + Call Dev_ExpectEQ(2000, parser_.year_) + + Call Dev_NewCase("Parse 13.01.00") + Call Dev_AssertTrue(parser_.Parse("13.01.00")) + Call Dev_ExpectEQ(13, parser_.day_) + Call Dev_ExpectEQ(1, parser_.month_) + Call Dev_ExpectEQ(0, parser_.year_) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function diff --git a/src/test/s_Path.cls b/src/test/s_Path.cls new file mode 100644 index 0000000..99258aa --- /dev/null +++ b/src/test/s_Path.cls @@ -0,0 +1,209 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "s_Path" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Private path_ As API_Path + +Public Function Setup() + ' Mandatory setup function + Set path_ = New API_Path +End Function + +Public Function Teardown() + ' Mandatory teardown function +End Function + +Public Function t_FromString() + On Error GoTo PROPAGATE_ERROR + + Call Dev_ExpectEQ("", path_.FromString("").Text, "Empty string") + Call Dev_ExpectEQ("???.txt", path_.FromString("???.txt").Text, "Invalid string") + Call Dev_ExpectEQ("D:\Dev\test.txt", path_.FromString("D:\Dev\test.txt").Text, "Global drive") + Call Dev_ExpectEQ("..\local\test.txt", path_.FromString("..\local\test.txt").Text, "Local file") + Call Dev_ExpectEQ("C:\LocalFolder", path_.FromString("C:\LocalFolder").Text, "Folder") + Call Dev_ExpectEQ("%APPDATA%\text.txt", path_.FromString("%APPDATA%\text.txt").Text, "Environ") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_AccessProperties() + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Empty") + Call path_.FromString("") + Call Dev_ExpectEQ("", path_.ParentFolder, "ParentFolder") + Call Dev_ExpectEQ("", path_.FileName, "FileName") + Call Dev_ExpectEQ("", path_.BaseName, "BaseName") + Call Dev_ExpectEQ("", path_.Extension, "Extension") + + Call Dev_NewCase("Invalid") + Call path_.FromString("invalid?&.!") + Call Dev_ExpectEQ("", path_.ParentFolder, "ParentFolder") + Call Dev_ExpectEQ("invalid?&.!", path_.FileName, "FileName") + Call Dev_ExpectEQ("invalid?&", path_.BaseName, "BaseName") + Call Dev_ExpectEQ("!", path_.Extension, "Extension") + + Call Dev_NewCase("Valid file") + Call path_.FromString("\\fs1.concept.ru\projects\test.txt") + Call Dev_ExpectEQ("\\fs1.concept.ru\projects", path_.ParentFolder, "ParentFolder") + Call Dev_ExpectEQ("test.txt", path_.FileName, "FileName") + Call Dev_ExpectEQ("test", path_.BaseName, "BaseName") + Call Dev_ExpectEQ("txt", path_.Extension, "Extension") + + Call Dev_NewCase("Valid folder") + Call path_.FromString("C:\Windows\test") + Call Dev_ExpectEQ("C:\Windows", path_.ParentFolder, "ParentFolder") + Call Dev_ExpectEQ("test", path_.FileName, "FileName") + Call Dev_ExpectEQ("test", path_.BaseName, "BaseName") + Call Dev_ExpectEQ("", path_.Extension, "Extension") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_ToGlobal() + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Empty") + Call path_.FromString("") + Call Dev_ExpectEQ("", path_.ToGlobal("").Text) + + Call Dev_NewCase("Invalid") + Call path_.FromString("invalid?&.!") + Call Dev_ExpectEQ("\invalid?&.!", path_.ToGlobal("").Text) + Call path_.FromString("invalid?&.!") + Call Dev_ExpectEQ("\\fs1.concept.ru\invalid?&.!", path_.ToGlobal("\\fs1.concept.ru").Text) + + Call Dev_NewCase("Global to Global") + Call path_.FromString("\\fs1.concept.ru\projects\test.txt") + Call Dev_ExpectEQ("\\fs1.concept.ru\projects\test.txt", path_.ToGlobal("\\fs1.concept.ru").Text) + + Call Dev_NewCase("Environ to Global") + Call path_.FromString("%APPDATA%\test.txt") + Call Dev_ExpectEQ(VBA.Environ$("APPDATA") & "\test.txt", path_.ToGlobal("\\fs1.concept.ru").Text) + Call path_.FromString("%INVALID_VARIABLE%\test.txt") + Call Dev_ExpectEQ("\test.txt", path_.ToGlobal("\\fs1.concept.ru").Text) + + Call Dev_NewCase("Local to Global") + Call path_.FromString("test.txt") + Call Dev_ExpectEQ("\\fs1.concept.ru\test.txt", path_.ToGlobal("\\fs1.concept.ru").Text) + Call path_.FromString("..\test.txt") + Call Dev_ExpectEQ("\\fs1.concept.ru\1\2\test.txt", path_.ToGlobal("\\fs1.concept.ru\1\2\3").Text) + Call path_.FromString("..\..\test.txt") + Call Dev_ExpectEQ("\\fs1.concept.ru\1\test.txt", path_.ToGlobal("\\fs1.concept.ru\1\2\3").Text) + Call Dev_ExpectEQ("\\fs1.concept.ru\1\test.txt", path_.Text) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_ToLocal() + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Empty") + Call path_.FromString("") + Call Dev_ExpectEQ("", path_.ToLocal("").Text) + + Call Dev_NewCase("Invalid") + Call path_.FromString("invalid?&.!") + Call Dev_ExpectEQ("invalid?&.!", path_.ToLocal("").Text) + Call path_.FromString("invalid?&.!") + Call Dev_ExpectEQ("invalid?&.!", path_.ToLocal("\\fs1.concept.ru").Text) + + Call Dev_NewCase("Global to Local") + Call path_.FromString("\\fs1.concept.ru\projects\test.txt") + Call Dev_ExpectEQ("test.txt", path_.Clone().ToLocal("\\fs1.concept.ru\projects").Text) + Call Dev_ExpectEQ("..\test.txt", path_.Clone().ToLocal("\\fs1.concept.ru\projects\1").Text) + Call Dev_ExpectEQ("projects\test.txt", path_.Clone().ToLocal("\\fs1.concept.ru").Text) + Call Dev_ExpectEQ("..\..\projects\test.txt", path_.Clone().ToLocal("\\fs1.concept.ru\1\2").Text) + + Call Dev_NewCase("Environ to Local") + Call path_.FromString("%APPDATA%\test.txt") + Call Dev_ExpectEQ("%APPDATA%\test.txt", path_.ToLocal("\\fs1.concept.ru").Text) + + Call Dev_NewCase("Local to Local") + Call path_.FromString("test.txt") + Call Dev_ExpectEQ("test.txt", path_.ToLocal("\\fs1.concept.ru").Text) + Call path_.FromString("..\test.txt") + Call Dev_ExpectEQ("..\test.txt", path_.ToLocal("\\fs1.concept.ru\1\2\3").Text) + Call path_.FromString("..\..\test.txt") + Call Dev_ExpectEQ("..\..\test.txt", path_.ToLocal("\\fs1.concept.ru\1\2\3").Text) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_ToServer() + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Empty") + Call path_.FromString("") + Call Dev_ExpectEQ("", path_.ToServer("").Text) + Call Dev_ExpectEQ("", path_.GlobalToServer().Text) + + Call Dev_NewCase("Invalid") + Call path_.FromString("invalid?&.!") + Call Dev_ExpectEQ("\invalid?&.!", path_.ToServer("").Text) + Call Dev_ExpectEQ("\invalid?&.!", path_.GlobalToServer().Text) + Call path_.FromString("invalid?&.!") + Call Dev_ExpectEQ("\\fs1.concept.ru\invalid?&.!", path_.ToServer("\\fs1.concept.ru").Text) + + Call Dev_NewCase("Global to Server") + Call path_.FromString("C:\Windows\test.txt") + Call Dev_ExpectEQ("C:\Windows\test.txt", path_.Clone().ToServer("").Text) + Call Dev_ExpectEQ("C:\Windows\test.txt", path_.Clone().GlobalToServer().Text) + Call path_.FromString("P:\test\test.txt") + Call Dev_ExpectEQ("\\fs1.concept.ru\projects\test\test.txt", path_.Clone().ToServer("").Text) + Call Dev_ExpectEQ("\\fs1.concept.ru\projects\test\test.txt", path_.Clone().GlobalToServer().Text) + + Call Dev_NewCase("Environ to Server") + Call path_.FromString("%APPDATA%\test.txt") + Call Dev_ExpectEQ(path_.Clone.ToGlobal("").Text, path_.ToServer("").Text) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_CheckExistance() + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Empty") + Call path_.FromString("") + Call Dev_ExpectFalse(path_.LocalExists("")) + Call Dev_ExpectFalse(path_.GlobalExists()) + + Call Dev_NewCase("Invalid") + Call path_.FromString("invalid?&.!") + Call Dev_ExpectFalse(path_.LocalExists("")) + Call Dev_ExpectFalse(path_.LocalExists("\\fs1.concept.ru")) + Call Dev_ExpectFalse(path_.GlobalExists()) + + Call Dev_NewCase("Valid file") + Call path_.FromString(ThisWorkbook.FullName) + Call Dev_ExpectTrue(path_.LocalExists("")) + Call Dev_ExpectTrue(path_.LocalExists("\\fs1.concept.ru")) + Call Dev_ExpectTrue(path_.GlobalExists()) + + Call Dev_NewCase("Valid folder") + Call path_.FromString(path_.ParentFolder) + Call Dev_ExpectTrue(path_.LocalExists("")) + Call Dev_ExpectTrue(path_.LocalExists("\\fs1.concept.ru")) + Call Dev_ExpectTrue(path_.GlobalExists()) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function diff --git a/src/test/s_StaticHierarchy.cls b/src/test/s_StaticHierarchy.cls new file mode 100644 index 0000000..7657f11 --- /dev/null +++ b/src/test/s_StaticHierarchy.cls @@ -0,0 +1,124 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "s_StaticHierarchy" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Private iTree_ As CDS_StaticHierarchy + +Public Function Setup() + ' Mandatory setup function + Set iTree_ = New CDS_StaticHierarchy +End Function + +Public Function Teardown() + ' Mandatory teardown function +End Function + +Public Function t_SingleRoot() + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Empty") + Call Dev_ExpectEQ(0, iTree_.Size, "Count nodes") + Call Dev_ExpectEQ(0, iTree_.MaxDepth, "Max Depth") + + Call Dev_NewCase("Push root") + Dim iRoot As CDS_NodeSH + Set iRoot = iTree_.PushItem(1) + Call Dev_AssertNotNothing(iRoot) + Call Dev_ExpectEQ(1, iTree_.Size, "Add node") + Call Dev_ExpectEQ(iRoot, iTree_.nodes_(1), "Add node") + Call Dev_ExpectEQ(1, iTree_.MaxDepth, "Max Depth") + Call Dev_ExpectEQ(1, iRoot.id_, "ID") + Call Dev_ExpectEQ(1, iRoot.rank_, "Rank") + Call Dev_ExpectNothing(iRoot.parent_, "Parent") + Call Dev_ExpectEQ(0, iRoot.children_.Count, "Children") + Call Dev_ExpectNothing(iRoot.data_, "Payload") + + Call Dev_NewCase("Push regular structure") + Call Dev_ExpectNotNothing(iTree_.PushItem(2)) + Call Dev_ExpectNotNothing(iTree_.PushItem(3)) + Call Dev_ExpectNotNothing(iTree_.PushItem(4)) + Call Dev_ExpectNotNothing(iTree_.PushItem(2)) + Call Dev_ExpectEQ(5, iTree_.Size, "Count nodes") + Call Dev_ExpectEQ(4, iTree_.MaxDepth, "Max Depth") + Call Dev_ExpectEQ(2, iRoot.children_.Count, "Count children") + Call Dev_ExpectEQ(iRoot, iRoot.children_(1).parent_, "Parent assignment") + Call Dev_ExpectEQ(iRoot, iRoot.children_(2).parent_, "Parent assignment") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_MultipleRoots() + On Error GoTo PROPAGATE_ERROR + + Call iTree_.PushItem(2) + Call iTree_.PushItem(3) + + Call Dev_NewCase("New root same rank") + Call Dev_AssertNotNothing(iTree_.PushItem(2)) + Call Dev_ExpectNothing(iTree_.nodes_(3).parent_, "New root") + Call Dev_ExpectNothing(iTree_.nodes_(1).parent_, "Old root") + + Call Dev_NewCase("New root lower rank") + Call Dev_AssertNotNothing(iTree_.PushItem(1)) + Call Dev_AssertNotNothing(iTree_.PushItem(2)) + Call Dev_ExpectNothing(iTree_.nodes_(4).parent_, "New root") + Call Dev_ExpectNothing(iTree_.nodes_(1).parent_, "Old root") + Call Dev_ExpectEQ(1, iTree_.nodes_(4).children_.Count, "Do not assume old roots as children!") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_StructuralErrors() + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Invalid level gap") + Call iTree_.PushItem(10) + Call Dev_ExpectNothing(iTree_.PushItem(12)) + + Call Dev_ExpectNotNothing(iTree_.PushItem(11)) + Call Dev_ExpectNotNothing(iTree_.PushItem(12)) + Call Dev_ExpectNotNothing(iTree_.PushItem(13)) + Call Dev_ExpectNotNothing(iTree_.PushItem(11)) + Call Dev_ExpectNothing(iTree_.PushItem(13)) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_CountDescendants() + On Error GoTo PROPAGATE_ERROR + + Call iTree_.PushItem(1) ' 1: 1 + Call iTree_.PushItem(2) ' 2: 1.1 + Call iTree_.PushItem(3) ' 3: 1.1.1 + Call iTree_.PushItem(3) ' 4: 1.1.2 + Call iTree_.PushItem(1) ' 5: 2 + Call iTree_.PushItem(1) ' 6: 3 + Call iTree_.PushItem(2) ' 7: 3.1 + Call iTree_.PushItem(2) ' 8: 3.2 + + Call Dev_ExpectEQ(3, iTree_.nodes_(1).descendantsCount_, "Node 1") + Call Dev_ExpectEQ(2, iTree_.nodes_(2).descendantsCount_, "Node 2") + Call Dev_ExpectEQ(0, iTree_.nodes_(3).descendantsCount_, "Node 3") + Call Dev_ExpectEQ(0, iTree_.nodes_(4).descendantsCount_, "Node 4") + Call Dev_ExpectEQ(0, iTree_.nodes_(5).descendantsCount_, "Node 5") + Call Dev_ExpectEQ(2, iTree_.nodes_(6).descendantsCount_, "Node 6") + Call Dev_ExpectEQ(0, iTree_.nodes_(7).descendantsCount_, "Node 7") + Call Dev_ExpectEQ(0, iTree_.nodes_(8).descendantsCount_, "Node 8") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function diff --git a/src/test/s_TextEdit.cls b/src/test/s_TextEdit.cls new file mode 100644 index 0000000..f96eef1 --- /dev/null +++ b/src/test/s_TextEdit.cls @@ -0,0 +1,134 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "s_TextEdit" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +' ===== Test suite for date Parsing ======= +Option Explicit + +Private Const STYLE_TEST_PARA$ = "TestParagraph" +Private Const STYLE_TEST_CHAR$ = "TestCharacter" + +Private doc_ As Word.Document + +Public Function Setup() + ' Mandatory setup function + Set doc_ = ThisDocument +End Function + +Public Function Teardown() + ' Mandatory teardown function + Call doc_.Range.Delete + Call doc_.Range.Select + Call Word.Selection.ClearFormatting +End Function + +Public Function t_AdjustRange() + On Error GoTo PROPAGATE_ERROR + ' =========== 12345678900123456678 + doc_.Range = " """" 1" & vbNewLine & " " + + Call Dev_ExpectEQ("", WordAdjustRange(doc_.Range(0, 0)), "Start of document") + Call Dev_ExpectEQ(" ", WordAdjustRange(doc_.Range(1, 1)), "Zero length") + Call Dev_ExpectEQ(" ", WordAdjustRange(doc_.Range(1, 8)), "Valid word") + Call Dev_ExpectEQ(" ", WordAdjustRange(doc_.Range(2, 7)), "Partial word") + Call Dev_ExpectEQ(" ", WordAdjustRange(doc_.Range(1, 9)), "Ignore end space") + Call Dev_ExpectEQ(" """" ", WordAdjustRange(doc_.Range(1, 11)), "Quoted words") + Call Dev_ExpectEQ(""""" ", WordAdjustRange(doc_.Range(12, 13)), "Do not extend leading space") + Call Dev_ExpectEQ(" ", WordAdjustRange(doc_.Range(8, 9)), "Single space") + Call Dev_ExpectEQ(doc_.Paragraphs.First.Range, WordAdjustRange(doc_.Paragraphs.First.Range), "Whole paragraph") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_AddText() + On Error GoTo PROPAGATE_ERROR + + doc_.Range = "1" & vbNewLine & "2" & vbNewLine & "3" + + Call Dev_NewCase("Put text in position") + Dim sTxt1$: sTxt1 = "testI" + Dim firstPara As Word.Range: Set firstPara = doc_.Paragraphs(1).Range + Dim insertedRes As Word.Range: Set insertedRes = WordPutText(sTxt1, doc_, 0) + Call Dev_ExpectEQ(sTxt1, insertedRes.Text) + Call Dev_ExpectEQ(Len(sTxt1) + 2, firstPara.End) + + Call Dev_NewCase("Add text after") + Dim secondPara As Word.Range: Set secondPara = doc_.Paragraphs(2).Range + Dim sTxt2$: sTxt2 = "testII" + Set insertedRes = WordAddLine(sTxt2, secondPara, STYLE_TEST_PARA) + Call insertedRes.MoveEnd(wdCharacter, -1) + Call Dev_ExpectEQ(doc_.Paragraphs(2).Range.End, secondPara.End) + Call Dev_ExpectEQ(secondPara.End, insertedRes.Start) + Call Dev_ExpectEQ(sTxt2, insertedRes.Text) + Call Dev_ExpectEQ(STYLE_TEST_PARA, insertedRes.Style) + + Call Dev_NewCase("Append text") + Dim sTxt3$: sTxt3 = "testIII" + Set secondPara = secondPara.Characters.First + Set insertedRes = WordAppendTo(sTxt3, secondPara, STYLE_TEST_CHAR) + Call Dev_ExpectEQ(doc_.Paragraphs(2).Range.Characters.First.End + Len(sTxt3), secondPara.End) + Call Dev_ExpectEQ("2" & sTxt3, secondPara.Text) + Call Dev_ExpectNE(STYLE_TEST_CHAR, secondPara.Characters.First) + Call Dev_ExpectEQ(insertedRes.Text, sTxt3) + Call Dev_ExpectEQ(STYLE_TEST_CHAR, insertedRes.Style) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_AddTextToEnd() + On Error GoTo PROPAGATE_ERROR + + Call WordAddLine("test1", doc_.Content) + Call Dev_ExpectEQ(2, doc_.Content.Paragraphs.Count) + + Call WordAppendTo("test2", doc_.Content) + Call Dev_ExpectEQ(2, doc_.Content.Paragraphs.Count, "Append to empty line at document end") + + Call WordAppendTo("test3", doc_.Content) + Call Dev_ExpectEQ(2, doc_.Content.Paragraphs.Count, "Append to non-empty line at document end") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_AddTextToTable() + On Error GoTo PROPAGATE_ERROR + Dim sTxt$: sTxt = "test" + + Dim aTable As Word.Table: Set aTable = doc_.Tables.Add(doc_.Content, 1, 2) + aTable.Cell(1, 1).Range = "123" + Dim newRng As Word.Range: Set newRng = WordAddLine(sTxt, aTable.Cell(1, 1).Range, STYLE_TEST_PARA) + Dim cellRng As Word.Range: Set cellRng = aTable.Cell(1, 1).Range + Call Dev_AssertEQ(2, cellRng.Paragraphs.Count) + Call Dev_ExpectEQ(sTxt, newRng.Text, "Add text after") + Call Dev_ExpectEQ(STYLE_TEST_PARA, newRng.Style, "Add text after") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_StyleExists() + On Error GoTo PROPAGATE_ERROR + + Call Dev_ExpectFalse(WordStyleExists(doc_, ""), "Empty input") + Call Dev_ExpectFalse(WordStyleExists(doc_, "Invalid1234567"), "Invalid style") + Call Dev_ExpectTrue(WordStyleExists(doc_, doc_.Paragraphs.First.Range.Style), "Default style") + Call Dev_ExpectTrue(WordStyleExists(doc_, doc_.Paragraphs.First.Range.Style.NameLocal), "Default style local") + Call Dev_ExpectTrue(WordStyleExists(doc_, STYLE_TEST_PARA), "Paragraph style") + Call Dev_ExpectTrue(WordStyleExists(doc_, STYLE_TEST_CHAR), "Character style") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function diff --git a/src/test/s_UndoWrapper.cls b/src/test/s_UndoWrapper.cls new file mode 100644 index 0000000..72204c8 --- /dev/null +++ b/src/test/s_UndoWrapper.cls @@ -0,0 +1,98 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "s_UndoWrapper" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +' ===== Test suite for UndoWrapper ======= +Option Explicit + +Private wrapper_ As API_UndoWrapper +Private targetPage_ As Visio.Page + +Public Function Setup() + ' Mandatory setup function + Set wrapper_ = New API_UndoWrapper + Call wrapper_.Init(ThisDocument.Application) + Set targetPage_ = ThisDocument.Pages(1) +End Function + +Public Function Teardown() + ' Mandatory teardown function + Call ThisDocument.Application.PurgeUndo +End Function + +Public Function t_BasicUndo() + On Error GoTo PROPAGATE_ERROR + + Dim oldCount&: oldCount = targetPage_.Shapes.Count + + Call wrapper_.BeginScope("Test") + Call AddTwoRectangles + Call wrapper_.EndScope + + Call targetPage_.Application.Undo + Call Dev_ExpectEQ(oldCount, targetPage_.Shapes.Count) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_CancelOnEndScope() + On Error GoTo PROPAGATE_ERROR + + Dim oldCount&: oldCount = targetPage_.Shapes.Count + + Call wrapper_.BeginScope("Test") + Call AddTwoRectangles + Call wrapper_.EndScope(bCommit:=False) + + Call Dev_ExpectEQ(oldCount, targetPage_.Shapes.Count) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_EndBeforeStart() + On Error GoTo PROPAGATE_ERROR + + Call wrapper_.EndScope + Call wrapper_.EndScope(bCommit:=False) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_DoubleStart() + On Error GoTo PROPAGATE_ERROR + + Dim oldCount&: oldCount = targetPage_.Shapes.Count + + Call wrapper_.BeginScope("Test") + Call wrapper_.BeginScope("Test") + Call AddTwoRectangles + Call wrapper_.BeginScope("Test2") + Call AddTwoRectangles + Call wrapper_.EndScope + + Call targetPage_.Application.Undo + Call Dev_ExpectEQ(oldCount, targetPage_.Shapes.Count, "Nested scope doesnt work. Only first scope is relevant") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +' ====== +Private Function AddTwoRectangles() + Dim oldCount&: oldCount = targetPage_.Shapes.Count + Call targetPage_.DrawRectangle(1, 1, 2, 2) + Call targetPage_.DrawRectangle(3, 3, 4, 4) + Call Dev_AssertEQ(oldCount + 2, targetPage_.Shapes.Count) +End Function diff --git a/src/test/s_VsoExtension.cls b/src/test/s_VsoExtension.cls new file mode 100644 index 0000000..d6b41aa --- /dev/null +++ b/src/test/s_VsoExtension.cls @@ -0,0 +1,34 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "s_VsoExtension" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +' ===== Test suite for Visio extension ======= +Option Explicit + +' TODO: +' Public Sub CC_CreateConnectors() +' Public Sub CC_RedirectConnectors() +' Public Function VsoAlignShapes(target As Collection, dGap As Double, bAlignTop As Boolean) + +Public Function Setup() + ' Mandatory setup function +End Function + +Public Function Teardown() + ' Mandatory teardown function +End Function + +Public Function t_Init() + On Error GoTo PROPAGATE_ERROR + + + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function diff --git a/src/test/s_VsoGraph.cls b/src/test/s_VsoGraph.cls new file mode 100644 index 0000000..500d0d6 --- /dev/null +++ b/src/test/s_VsoGraph.cls @@ -0,0 +1,89 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "s_VsoGraph" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +' ===== Test suite for Visio Graph scanner ======= +Option Explicit + +' TODO: +' Public Function ScanGraph(iSeed As Collection, bReverseLinks As Boolean) As CDS_Graph + +Private page_ As Visio.Page + +Public Function Setup() + ' Mandatory setup function + Set page_ = ThisDocument.Pages(1) + ThisDocument.DiagramServicesEnabled = visServiceStructureFull +End Function + +Public Function Teardown() + ' Mandatory teardown function + ThisDocument.DiagramServicesEnabled = 0 + Call ClearAll +End Function + +Public Function t_ScanConnector() + On Error GoTo PROPAGATE_ERROR + + Dim iResult As ItemConnector + + Call Dev_NewCase("Free arrow") + Dim iArrow As Visio.Shape: Set iArrow = page_.Drop(FindMaster(ThisDocument, MASTER_CONNECTOR), 30, 30) + iResult = ScanConnector(iArrow) + Call Dev_ExpectNothing(iResult.begin_) + Call Dev_ExpectNothing(iResult.end_) + Call Dev_ExpectEQ(T_CD_STRAIGHT, iResult.dir_) + + Call Dev_NewCase("Arrow directions") + iArrow.CellsU("BeginArrow") = 9 + iArrow.CellsU("EndArrow") = 9 + iResult = ScanConnector(iArrow) + Call Dev_ExpectEQ(T_CD_NONE, iResult.dir_) + + iArrow.CellsU("BeginArrow") = 9 + iArrow.CellsU("EndArrow") = 1 + iResult = ScanConnector(iArrow) + Call Dev_ExpectEQ(T_CD_STRAIGHT, iResult.dir_) + + iArrow.CellsU("BeginArrow") = 1 + iArrow.CellsU("EndArrow") = 9 + iResult = ScanConnector(iArrow) + Call Dev_ExpectEQ(T_CD_REVERSE, iResult.dir_) + + iArrow.CellsU("BeginArrow") = 1 + iArrow.CellsU("EndArrow") = 1 + iResult = ScanConnector(iArrow) + Call Dev_ExpectEQ(T_CD_MUTUAL, iResult.dir_) + + Call Dev_NewCase("Attached shapes") + Dim elem1 As Visio.Shape: Set elem1 = page_.Drop(FindMaster(ThisDocument, MASTER_ELEMENT), 10, 10) + Dim elem2 As Visio.Shape: Set elem2 = page_.Drop(FindMaster(ThisDocument, MASTER_ELEMENT), 20, 20) + iArrow.CellsU("BeginArrow") = 9 + iArrow.CellsU("EndArrow") = 1 + + Call iArrow.CellsU("BeginX").GlueTo(elem1.CellsU("PinX")) + iResult = ScanConnector(iArrow) + Call Dev_ExpectEQ(elem1, iResult.begin_) + Call Dev_ExpectNothing(iResult.end_) + + Call iArrow.Disconnect(visConnectorBothEnds, 0, 0, visFeet) + Call iArrow.CellsU("EndX").GlueTo(elem2.CellsU("PinX")) + iResult = ScanConnector(iArrow) + Call Dev_ExpectNothing(iResult.begin_) + Call Dev_ExpectEQ(elem2, iResult.end_) + + Call iArrow.CellsU("BeginX").GlueTo(elem1.CellsU("PinX")) + Call iArrow.CellsU("EndX").GlueTo(elem2.CellsU("PinX")) + iResult = ScanConnector(iArrow) + Call Dev_ExpectEQ(elem1, iResult.begin_) + Call Dev_ExpectEQ(elem2, iResult.end_) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function diff --git a/src/test/s_VsoUtilities.cls b/src/test/s_VsoUtilities.cls new file mode 100644 index 0000000..bc71305 --- /dev/null +++ b/src/test/s_VsoUtilities.cls @@ -0,0 +1,236 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "s_VsoUtilities" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +' ===== Test suite for Visio Utilities ======= +Option Explicit + +' Public Function VsoIncrementWidth(target As Visio.Shape, valueInMM&) + +Private page_ As Visio.Page + +Public Function Setup() + ' Mandatory setup function + Set page_ = ThisDocument.Pages(1) + ThisDocument.DiagramServicesEnabled = visServiceStructureFull +End Function + +Public Function Teardown() + ' Mandatory teardown function + ThisDocument.DiagramServicesEnabled = 0 + Call ClearAll +End Function + +Public Function t_FindMaster() + On Error GoTo PROPAGATE_ERROR + + Call Dev_ExpectNothing(FindMaster(ThisDocument, "Invalid"), "Invalid master name") + Call Dev_ExpectNothing(FindMaster(Nothing, MASTER_ELEMENT), "Invalid document") + Call Dev_ExpectEQ(ThisDocument.Masters.Item(MASTER_ELEMENT), FindMaster(ThisDocument, MASTER_ELEMENT), "Valid master") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_GetContainingShape() + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Element") + Dim elem As Visio.Shape: Set elem = page_.Drop(FindMaster(ThisDocument, MASTER_ELEMENT), 10, 10) + Dim iArrow As Visio.Shape: Set iArrow = page_.Drop(FindMaster(ThisDocument, MASTER_CONNECTOR), 30, 30) + Call Dev_ExpectNothing(GetContainingShape(elem)) + Call Dev_ExpectNothing(GetContainingShape(iArrow)) + + Call Dev_NewCase("Container") + Dim iContainer As Visio.Shape: Set iContainer = page_.Drop(FindMaster(ThisDocument, MASTER_CONTAINER), 20, 20) + Call Dev_ExpectNothing(GetContainingShape(iContainer)) + Call iContainer.ContainerProperties.AddMember(elem, visMemberAddExpandContainer) + Call iContainer.ContainerProperties.AddMember(iArrow, visMemberAddExpandContainer) + Call Dev_ExpectNothing(GetContainingShape(iContainer)) + Call Dev_ExpectEQ(iContainer, GetContainingShape(elem)) + + Call Dev_NewCase("List") + Dim iList As Visio.Shape: Set iList = page_.Drop(FindMaster(ThisDocument, MASTER_LIST), 0, 0) + Call iList.ContainerProperties.AddMember(iContainer, visMemberAddExpandContainer) + Call Dev_ExpectNothing(GetContainingShape(iList)) + Call Dev_ExpectEQ(iContainer, GetContainingShape(elem)) + Call Dev_ExpectEQ(iContainer, GetContainingShape(iArrow)) + Call Dev_ExpectEQ(iList, GetContainingShape(iContainer)) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_VsoShapeExists() + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Empty page") + Call Dev_ExpectFalse(VsoShapeExists(vbNullString, page_)) + Call Dev_ExpectFalse(VsoShapeExists("", page_)) + Call Dev_ExpectFalse(VsoShapeExists("invalid", page_)) + + Call Dev_NewCase("Valid shape") + Dim elem As Visio.Shape: Set elem = page_.Drop(FindMaster(ThisDocument, MASTER_ELEMENT), 10, 10) + Call Dev_ExpectFalse(VsoShapeExists("", page_), "Empty input") + Call Dev_ExpectFalse(VsoShapeExists("Test", page_), "Invalid name") + Call Dev_ExpectTrue(VsoShapeExists(elem.Name, page_), "Default name") + elem.Name = "Test1337" + Call Dev_ExpectTrue(VsoShapeExists(elem.Name, page_), "Custom name") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_VsoCalculateFillingFor() + On Error GoTo PROPAGATE_ERROR + + Call Dev_ExpectEQ(0, VsoCalculateFillingFor(page_), "Empty page") + + Call Dev_NewCase("Arrow") + Dim iArrow As Visio.Shape: Set iArrow = page_.Drop(FindMaster(ThisDocument, MASTER_CONNECTOR), 0, 0) + Call Dev_ExpectEQ(0, VsoCalculateFillingFor(page_)) + + Call Dev_NewCase("Valid shape") + Dim elem As Visio.Shape: Set elem = page_.Drop(FindMaster(ThisDocument, MASTER_ELEMENT), 0, 0) + Call Dev_ExpectNE(0, VsoCalculateFillingFor(page_)) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_VsoIsListContainer() + On Error GoTo PROPAGATE_ERROR + + Dim testShape As Visio.Shape + + Call Dev_NewCase("Element") + Set testShape = page_.Drop(FindMaster(ThisDocument, MASTER_ELEMENT), 0, 0) + Call Dev_ExpectFalse(VsoIsListContainer(testShape)) + Call testShape.Delete + + Call Dev_NewCase("Arrow") + Set testShape = page_.Drop(FindMaster(ThisDocument, MASTER_CONNECTOR), 0, 0) + Call Dev_ExpectFalse(VsoIsListContainer(testShape)) + Call testShape.Delete + + Call Dev_NewCase("Container") + Set testShape = page_.Drop(FindMaster(ThisDocument, MASTER_CONTAINER), 0, 0) + Call Dev_ExpectFalse(VsoIsListContainer(testShape)) + Call testShape.Delete + + Call Dev_NewCase("List") + Set testShape = page_.Drop(FindMaster(ThisDocument, MASTER_LIST), 0, 0) + Call Dev_ExpectTrue(VsoIsListContainer(testShape)) + Call testShape.Delete + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_VsoIsMovable() + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Element") + Dim elem As Visio.Shape: Set elem = page_.Drop(FindMaster(ThisDocument, MASTER_ELEMENT), 10, 10) + Dim iArrow As Visio.Shape: Set iArrow = page_.Drop(FindMaster(ThisDocument, MASTER_CONNECTOR), 30, 30) + Call Dev_ExpectTrue(VsoIsMovable(elem)) + Call Dev_ExpectFalse(VsoIsMovable(iArrow)) + + Call Dev_NewCase("Container") + Dim iContainer As Visio.Shape: Set iContainer = page_.Drop(FindMaster(ThisDocument, MASTER_CONTAINER), 20, 20) + Call Dev_ExpectFalse(VsoIsMovable(iContainer)) + Call iContainer.ContainerProperties.AddMember(elem, visMemberAddExpandContainer) + Call Dev_ExpectTrue(VsoIsMovable(elem)) + + Call Dev_NewCase("List") + Set elem = page_.Drop(FindMaster(ThisDocument, MASTER_ELEMENT), 0, 0) + Dim iList As Visio.Shape: Set iList = page_.Drop(FindMaster(ThisDocument, MASTER_LIST), 0, 0) + Call iList.ContainerProperties.InsertListMember(elem, 1) + Call Dev_ExpectTrue(VsoIsMovable(iList)) + Call Dev_ExpectFalse(VsoIsMovable(elem)) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_VsoIsConnected() + On Error GoTo PROPAGATE_ERROR + + Dim elem1 As Visio.Shape: Set elem1 = page_.Drop(FindMaster(ThisDocument, MASTER_ELEMENT), 10, 10) + Dim elem2 As Visio.Shape: Set elem2 = page_.Drop(FindMaster(ThisDocument, MASTER_ELEMENT), 20, 20) + Dim iArrow As Visio.Shape: Set iArrow = page_.Drop(FindMaster(ThisDocument, MASTER_CONNECTOR), 30, 30) + + Call Dev_NewCase("Not connected") + Call Dev_ExpectFalse(VsoIsConnected(elem1, elem1), "Self") + Call Dev_ExpectFalse(VsoIsConnected(iArrow, elem1), "Connector") + Call Dev_ExpectFalse(VsoIsConnected(elem2, iArrow), "Connector") + + Call Dev_NewCase("Valid connection") + Call elem1.AutoConnect(elem2, visAutoConnectDirNone) + Call Dev_ExpectTrue(VsoIsConnected(elem1, elem2), "Straight connection") + Call Dev_ExpectFalse(VsoIsConnected(elem2, elem1), "Reverse connection") + Call Dev_ExpectFalse(VsoIsConnected(elem1, elem1), "Self connection") + + Call Dev_NewCase("Self connection") + Call iArrow.CellsU("BeginX").GlueTo(elem1.CellsU("PinX")) + Call iArrow.CellsU("EndX").GlueTo(elem1.CellsU("PinX")) + Call Dev_ExpectTrue(VsoIsConnected(elem1, elem1)) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_VsoGeometricSort() + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Empty input") + Dim ids() As Long + On Error Resume Next + Call VsoGeometricSort(ids, page_) + Call Dev_ExpectNoError + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Valid elementary") + Dim testIDs() As Long: ReDim testIDs(1 To 4) + ReDim ids(1 To 4) + Dim iShape1 As Visio.Shape: Set iShape1 = page_.Drop(FindMaster(ThisDocument, MASTER_ELEMENT), 0, 0) + Dim iShape2 As Visio.Shape: Set iShape2 = page_.Drop(FindMaster(ThisDocument, MASTER_ELEMENT), 0, 1) + Dim iShape3 As Visio.Shape: Set iShape3 = page_.Drop(FindMaster(ThisDocument, MASTER_ELEMENT), 1, 0) + Dim iShape4 As Visio.Shape: Set iShape4 = page_.Drop(FindMaster(ThisDocument, MASTER_ELEMENT), 1, 1) + ids(1) = iShape1.ID: testIDs(1) = iShape2.ID + ids(2) = iShape2.ID: testIDs(2) = iShape4.ID + ids(3) = iShape3.ID: testIDs(3) = iShape1.ID + ids(4) = iShape4.ID: testIDs(4) = iShape3.ID + Call VsoGeometricSort(ids, page_) + Call Dev_ExpectEQ(testIDs, ids) + + Call Dev_NewCase("Valid different shapes") + Set iShape1 = page_.DrawRectangle(0, 0, 10, 10) + Set iShape2 = page_.DrawRectangle(11, 5, 17, -5) + Set iShape3 = page_.DrawRectangle(0, 0, 10, 10 + 0.001) + Set iShape4 = page_.DrawRectangle(0, 6, 7, 16) + + ids(1) = iShape1.ID: testIDs(1) = iShape4.ID + ids(2) = iShape2.ID: testIDs(2) = iShape1.ID + ids(3) = iShape3.ID: testIDs(3) = iShape3.ID + ids(4) = iShape4.ID: testIDs(4) = iShape2.ID + + Call VsoGeometricSort(ids, page_) + Call Dev_ExpectEQ(testIDs, ids) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function diff --git a/src/test/s_VsoWrapper.cls b/src/test/s_VsoWrapper.cls new file mode 100644 index 0000000..fb6aa09 --- /dev/null +++ b/src/test/s_VsoWrapper.cls @@ -0,0 +1,98 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "s_VsoWrapper" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +' Note: Testing only functions different from API_WordWrapper +' TODO: test open document flags + +Private vso_ As API_VsoWrapper +Private fso_ As Scripting.FileSystemObject + +Public Function Setup() + ' Mandatory setup function + Set vso_ = New API_VsoWrapper + Call vso_.DisableMessages + Call vso_.CreateApplication + Set fso_ = New Scripting.FileSystemObject + Call EnsureFolderExists(Dev_GetTestFolder) +End Function + +Public Function Teardown() + ' Mandatory teardown function + Call fso_.DeleteFolder(Dev_GetTestFolder) + Call vso_.ReleaseApplication + Set vso_ = Nothing +End Function + +Public Function t_SaveAs() + On Error GoTo PROPAGATE_ERROR + + Dim sTemplate$: sTemplate = Dev_GetTestFolder & "\" & "testFile" + Dim sFile$: sFile = sTemplate & ".vsdx" + Dim sTarget$ + Dim oReader As New API_VsoWrapper + Call oReader.CreateApplication + Call oReader.DisableMessages + + Call Dev_NewCase("No document") + On Error Resume Next + Call Dev_ExpectFalse(vso_.SaveAs(sFile)) + Call Dev_ExpectNoError + On Error Resume Next + + Call vso_.NewDocument + Call vso_.Document.Pages(1).DrawRectangle(1, 1, 2, 2) + Call Dev_ExpectFalse(vso_.SaveAs("invalid!?"), "Invalid filename") + + Call Dev_NewCase("Save as vsdx") + sTarget = sTemplate & ".vsdx" + Call Dev_ExpectTrue(vso_.SaveAs(sTarget)) + Call vso_.ReleaseDocument(bCloseApplication:=False) + Call Dev_ExpectTrue(fso_.FileExists(sTarget), "File created") + Call Dev_AssertTrue(oReader.OpenDocument(sTarget), "Open saved document") + Call Dev_ExpectEQ(1, oReader.Document.Pages(1).Shapes.Count, "Validate contents") + Call oReader.ReleaseDocument(bCloseApplication:=False) + + Call Dev_NewCase("Save as vsdm") + Call vso_.OpenDocument(sFile) + sTarget = sTemplate & ".vsdm" + Call Dev_ExpectTrue(vso_.SaveAs(sTarget)) + Call vso_.ReleaseDocument(bCloseApplication:=False) + Call Dev_AssertTrue(fso_.FileExists(sTarget), "File created") + Call Dev_ExpectTrue(oReader.OpenDocument(sTarget), "Open saved document") + Call Dev_ExpectEQ(1, oReader.Document.Pages(1).Shapes.Count, "Validate contents") + Call oReader.ReleaseDocument(bCloseApplication:=False) + + Call Dev_NewCase("Save as vstx") + Call vso_.OpenDocument(sFile) + sTarget = sTemplate & ".vstx" + Call Dev_ExpectTrue(vso_.SaveAs(sTarget)) + Call vso_.ReleaseDocument(bCloseApplication:=False) + Call Dev_AssertTrue(fso_.FileExists(sTarget), "File created") + Call Dev_ExpectTrue(oReader.OpenDocument(sTarget), "Open saved document") + Call Dev_ExpectEQ(1, oReader.Document.Pages(1).Shapes.Count, "Validate contents") + Call oReader.ReleaseDocument(bCloseApplication:=False) + + Call Dev_NewCase("Save as vstm") + Call vso_.OpenDocument(sFile) + sTarget = sTemplate & ".vstm" + Call Dev_ExpectTrue(vso_.SaveAs(sTarget)) + Call vso_.ReleaseDocument(bCloseApplication:=False) + Call Dev_AssertTrue(fso_.FileExists(sTarget), "File created") + Call Dev_ExpectTrue(oReader.OpenDocument(sTarget), "Open saved document") + Call Dev_ExpectEQ(1, oReader.Document.Pages(1).Shapes.Count, "Validate contents") + Call oReader.ReleaseDocument(bCloseApplication:=False) + + Call oReader.ReleaseApplication + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function diff --git a/src/test/s_WordWrapper.cls b/src/test/s_WordWrapper.cls new file mode 100644 index 0000000..4827041 --- /dev/null +++ b/src/test/s_WordWrapper.cls @@ -0,0 +1,611 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "s_WordWrapper" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +' TODO: test reset paused status +' TODO: test reporter + +Private fso_ As Scripting.FileSystemObject +Private word_ As API_WordWrapper + +Public Function Setup() + ' Mandatory setup function + Set word_ = New API_WordWrapper + Call word_.DisableMessages + Call word_.CreateApplication + Set fso_ = New Scripting.FileSystemObject + Call EnsureFolderExists(Dev_GetTestFolder) +End Function + +Public Function Teardown() + ' Mandatory teardown function + Call fso_.DeleteFolder(Dev_GetTestFolder) + Call word_.ReleaseApplication + Set word_ = Nothing +End Function + +Public Function t_CreateApplication() + On Error GoTo PROPAGATE_ERROR + + Dim word1 As New API_WordWrapper + Dim word2 As New API_WordWrapper + + Call Dev_NewCase("Create visible application") + Call Dev_AssertNotNothing(word1.CreateApplication(bIsVisible:=True)) + Call Dev_ExpectTrue(word1.Application.Visible) + + Call Dev_NewCase("Create hidden application") + Call Dev_AssertNotNothing(word2.CreateApplication(bIsVisible:=False)) + Call Dev_ExpectFalse(word2.Application.Visible) + + Call Dev_NewCase("Create application twice") + Dim app1 As Word.Application: Set app1 = word1.Application + Call Dev_ExpectNotNothing(app1, "Create 1st application") + Dim app2 As Word.Application: Set app2 = word1.CreateApplication + Call Dev_ExpectNotNothing(app2, "Create 2nd application") + Call Dev_ExpectObjectValid(app1, "Do not close first application before creating new") + + Call word1.ReleaseApplication + Call word2.ReleaseApplication + Call app1.Quit + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_SetApplication() + On Error GoTo PROPAGATE_ERROR + + Dim word1 As New API_WordWrapper + Dim word2 As New API_WordWrapper + Dim app1 As Word.Application: Set app1 = word1.CreateApplication(bIsVisible:=True) + + Call Dev_NewCase("Set application valid") + Call word2.SetApplication(app1) + Call Dev_ExpectEQ(app1, word2.Application) + + Call Dev_NewCase("Set application nothing") + Call word2.SetApplication(Nothing) + Call Dev_ExpectNothing(word2.Application) + + Call Dev_NewCase("Set self") + Call word1.SetApplication(word1.Application) + Call word1.ReleaseApplication + Call Dev_ExpectObjectInvalid(app1, "Do not reset ownership on self set") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_Destructor() + On Error GoTo PROPAGATE_ERROR + + Dim word1 As New API_WordWrapper + + Call Dev_NewCase("No application") + On Error Resume Next + Set word1 = Nothing + Call Dev_ExpectNoError + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Owned empty application") + Dim app1 As Word.Application: Set app1 = word1.CreateApplication + Set word1 = Nothing + Call Sleep(500) + Call Dev_ExpectObjectInvalid(app1) + + Call Dev_NewCase("Owned application with document") + Set word1 = New API_WordWrapper + Set app1 = word1.CreateApplication + Dim doc1 As Word.Document: Set doc1 = word1.NewDocument + Set word1 = Nothing + Call Dev_ExpectObjectValid(doc1, "Do not close document") + Call Dev_ExpectObjectValid(app1, "Do not close application") + + Call app1.Quit(SaveChanges:=False) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_ReleaseApplication() + On Error GoTo PROPAGATE_ERROR + + Dim word1 As New API_WordWrapper + Dim word2 As New API_WordWrapper + + Call Dev_NewCase("No application") + On Error Resume Next + Call word1.ReleaseApplication + Call Dev_ExpectNoError + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Owned application") + Dim app1 As Word.Application: Set app1 = word1.CreateApplication + Call word1.ReleaseApplication + Call Dev_ExpectNothing(word1.Application, "Reset application") + Call Dev_ExpectObjectInvalid(app1, "Close application") + + Call Dev_NewCase("Non-owned application") + Set app1 = word1.CreateApplication + Call word2.SetApplication(app1) + Call word2.ReleaseApplication + Call Dev_ExpectNothing(word2.Application, "Reset application") + Call Dev_ExpectObjectValid(app1, "Do not close application") + Call word1.ReleaseApplication + + Call Dev_NewCase("Owned application with owned open document") + Set app1 = word1.CreateApplication + Dim doc1 As Word.Document: Set doc1 = word1.NewDocument + Call word1.ReleaseApplication + Call Dev_ExpectNothing(word1.Application, "Reset application") + Call Dev_ExpectObjectInvalid(doc1, "Close document") + Call Dev_ExpectObjectInvalid(app1, "Close application") + + Call Dev_NewCase("Owned application with non-owned open document") + Set app1 = word1.CreateApplication + Call word2.SetApplication(app1) + Set doc1 = word2.NewDocument + Call word1.ReleaseApplication + Call Dev_ExpectNothing(word1.Application, "Reset application") + Call Dev_ExpectNotNothing(word2.Application, "Do not reset shared application") + Call Dev_ExpectObjectInvalid(doc1, "Close document") + Call Dev_ExpectObjectInvalid(app1, "Close application") + + On Error Resume Next + Set word2 = Nothing + Call Dev_ExpectNoError("Terminate correctly invalid application") + On Error GoTo PROPAGATE_ERROR + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_NewDocument() + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Default template") + Call Dev_ExpectNotNothing(word_.NewDocument) + Call word_.ReleaseDocument(bCloseApplication:=False) + + Call Dev_NewCase("Custom template") + Dim sTemplate$: sTemplate = Dev_GetArtifactFolder & "\" & TEST_WORD_TEMPLATE + Call Dev_AssertNotNothing(word_.NewDocument(sTemplate)) + Call Dev_ExpectEQ("test123", word_.Document.Words.First.Text) + Call word_.ReleaseDocument(bCloseApplication:=False) + + Call Dev_NewCase("Missing template") + Call Dev_ExpectNothing(word_.NewDocument(sTemplate:="invalid", bDefaultIfFail:=False), "No deafult fallback") + Call Dev_AssertNotNothing(word_.NewDocument(sTemplate:="invalid", bDefaultIfFail:=True), "Default fallback") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_SetDocument() + On Error GoTo PROPAGATE_ERROR + + Dim word1 As New API_WordWrapper + Dim word2 As New API_WordWrapper + + Call Dev_NewCase("Set nothing to nothing") + On Error Resume Next + Call word_.SetDocument(Nothing) + Call Dev_ExpectNoError("No error") + On Error GoTo PROPAGATE_ERROR + Call Dev_ExpectNothing(word_.Document, "Document is set to nothing") + Call Dev_ExpectNotNothing(word_.Application, "Application is not reset") + + Call Dev_NewCase("Set valid document") + Call word1.SetApplication(word_.Application) + Dim doc1 As Word.Document: Set doc1 = word1.NewDocument + Call word_.SetDocument(doc1) + Call Dev_ExpectEQ(doc1, word_.Document, "Check document") + Call word_.SetDocument(Nothing) + Call Dev_ExpectNothing(word_.Document, "Set valid to nothing") + Call word1.ReleaseDocument(bCloseApplication:=False) + + Call Dev_NewCase("Set document from another application") + Call word2.CreateApplication + Call word1.SetApplication(word_.Application) + Set doc1 = word2.NewDocument + Call word1.SetDocument(doc1) + Call Dev_ExpectEQ(doc1, word1.Document, "Check document") + Call Dev_ExpectEQ(word2.Application, word1.Application, "Update application document") + Call word1.ReleaseDocument(bCloseApplication:=False) + Call Dev_ExpectObjectValid(doc1, "Do not transfer ownership") + Call word2.ReleaseApplication + + Call Dev_NewCase("Transfer document ownership") + Call word1.SetApplication(word_.Application) + Set doc1 = word1.NewDocument + Call word_.SetDocument(doc1, bOwnership:=True) + Call Dev_ExpectEQ(doc1, word_.Document, "Check document") + Call word_.ReleaseDocument(bCloseApplication:=False) + Call Dev_ExpectObjectInvalid(doc1, "Transfer ownership") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_OpenDocumentRead() + On Error GoTo PROPAGATE_ERROR + + Dim word1 As New API_WordWrapper: Call word1.DisableMessages + Dim word2 As New API_WordWrapper: Call word2.DisableMessages + Dim doc1 As Word.Document + Dim doc2 As Word.Document + Dim sFile$: sFile = Dev_GetArtifactFolder & "\" & TEST_WORD_DOCUMENT + Dim sInvalid$: sInvalid = Dev_GetArtifactFolder & "\" & TEST_WORD_INVALID + Dim sText$: sText = "TestWord" + + Call Dev_NewCase("Invalid filename") + Call Dev_ExpectNothing(word1.OpenDocument("invalid", bReadOnly:=True), "Wrapper without application") + Call Dev_ExpectNothing(word1.Application, "Do not open application for invalid document") + Call Dev_ExpectNothing(word_.OpenDocument("invalid", bReadOnly:=True), "Wrapper with application") + Call Dev_ExpectObjectValid(word_.Application, "Do not reset application after failed document open") + + Call Dev_NewCase("Invalid file format") + Call Dev_ExpectNothing(word1.OpenDocument(sInvalid, bReadOnly:=True), "Wrapper without application") + Call Dev_ExpectNothing(word1.Application, "Do not open application for invalid document") + Call Dev_ExpectNothing(word_.OpenDocument(sInvalid, bReadOnly:=True), "Wrapper with application") + Call Dev_ExpectObjectValid(word_.Application, "Do not reset application after failed document open") + + Call Dev_NewCase("Valid file - existing application") + Call Dev_AssertNotNothing(word_.OpenDocument(sFile, bReadOnly:=True)) + Call Dev_ExpectEQ(sText, word_.Document.Words.First.Text) + Call Dev_ExpectNothing(word_.OpenDocument(sFile, bReadOnly:=True), "Cannot open another document while one is open already") + Call word_.ReleaseDocument(bCloseApplication:=False) + + Call Dev_NewCase("Valid file - borrowing application") + Call Dev_AssertNotNothing(word1.OpenDocument(sFile, bReadOnly:=True)) + Call Dev_ExpectEQ(sText, word1.Document.Words.First.Text) + Call Dev_ExpectEQ(word_.Application, word1.Application, "Already open application should be used") + Call word1.ReleaseDocument + Call word1.ReleaseApplication + Call Dev_ExpectObjectValid(word_.Application, "Do not claim ownership of borrowed application") + + Call Dev_NewCase("Valid file - no application") + Call word_.ReleaseApplication + Call Dev_AssertNotNothing(word_.OpenDocument(sFile, bReadOnly:=True)) + Call Dev_ExpectObjectValid(word_.Application, "Valid application") + Call word_.ReleaseDocument(bCloseApplication:=False) + + Call Dev_NewCase("Valid file - already open READONLY in same application") + Set doc1 = word_.OpenDocument(sFile, bReadOnly:=True) + Call word2.SetApplication(word_.Application) + Set doc2 = word2.OpenDocument(sFile, bReadOnly:=True) + Call Dev_AssertNotNothing(doc2, "Open file") + Call Dev_ExpectEQ(sText, doc2.Words.First.Text) + Call Dev_ExpectEQ(ObjPtr(doc1), ObjPtr(doc2), "Do not reopen same document") + Call word2.ReleaseDocument + Call Dev_ExpectObjectValid(doc1, "Do not claim ownership of borrowed document") + Call word_.ReleaseDocument(bCloseApplication:=False) + + Call Dev_NewCase("Valid file - already open MODIFY in same application") + Set doc1 = word_.OpenDocument(sFile, bReadOnly:=False) + Call word2.SetApplication(word_.Application) + Set doc2 = word2.OpenDocument(sFile, bReadOnly:=True) + Call Dev_AssertNotNothing(doc2, "Open file") + Call Dev_ExpectEQ(sText, doc2.Words.First.Text) + Call Dev_ExpectEQ(ObjPtr(doc1), ObjPtr(doc2), "Do not reopen same document") + Call word2.ReleaseDocument + Call Dev_ExpectObjectValid(doc1, "Do not claim ownership of borrowed document") + Call word_.ReleaseDocument(bCloseApplication:=False) + + Call Dev_NewCase("Valid file - already open in another application") + Call word2.CreateApplication + Set doc2 = word2.OpenDocument(sFile, bReadOnly:=True) + Call Dev_ExpectEQ(sText, doc2.Words.First.Text) + Call Dev_ExpectNE(ObjPtr(doc1), ObjPtr(doc2), "Reopen document in new application") + Call word2.ReleaseDocument + Call word2.ReleaseApplication + + Call word_.ReleaseDocument + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_OpenDocumentModify() + On Error GoTo PROPAGATE_ERROR + + Dim word1 As New API_WordWrapper: Call word1.DisableMessages + Dim word2 As New API_WordWrapper: Call word2.DisableMessages + Dim doc1 As Word.Document + Dim doc2 As Word.Document + Dim sFile$: sFile = Dev_GetTestFolder & "\" & TEST_WORD_DOCUMENT + Dim sText1$: sText1 = "TestWord" + Dim sText2$: sText2 = "Test1Word2" + + Call fso_.CopyFile(Dev_GetArtifactFolder & "\" & TEST_WORD_DOCUMENT, sFile) + + Call Dev_NewCase("Valid file - existing application") + Call Dev_AssertNotNothing(word_.OpenDocument(sFile, bReadOnly:=False)) + Call Dev_ExpectEQ(sText1, word_.Document.Words.First.Text, "Validate opened file") + word_.Document.Words.First = sText2 + Call word_.ReleaseDocument(bCloseApplication:=False, bSaveChanges:=True) + Call Dev_AssertNotNothing(word_.OpenDocument(sFile, bReadOnly:=True)) + Call Dev_ExpectEQ(sText2, word_.Document.Words.First.Text, "Confirm modification") + Call word_.ReleaseDocument(bCloseApplication:=False) + + Call Dev_NewCase("Readonly file") + fso_.GetFile(sFile).Attributes = ReadOnly + Call Dev_ExpectNothing(word_.OpenDocument(sFile, bReadOnly:=False)) + Call Dev_ExpectNotNothing(word_.OpenDocument(sFile, bReadOnly:=True)) + Call word_.ReleaseDocument(bCloseApplication:=False) + fso_.GetFile(sFile).Attributes = Normal + + Call Dev_NewCase("Valid file - already open MODIFY in same application") + Call word_.OpenDocument(sFile, bReadOnly:=False) + Call word2.SetApplication(word_.Application) + Set doc2 = word2.OpenDocument(sFile, bReadOnly:=False) + Call Dev_ExpectNotNothing(doc2, "Find already opened file") + Call Dev_ExpectEQ(ObjPtr(word_.Document), ObjPtr(doc2), "Get same document") + Call word2.ReleaseDocument(bCloseApplication:=False) + Call word_.ReleaseDocument(bCloseApplication:=False) + + Call Dev_NewCase("Valid file - already open READONLY in same application") + Call word_.OpenDocument(sFile, bReadOnly:=True) + Call word2.SetApplication(word_.Application) + Call Dev_ExpectNothing(word2.OpenDocument(sFile, bReadOnly:=False), "Do not open blocked document") + Call word_.ReleaseDocument(bCloseApplication:=False) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_ReleaseDocument() + On Error GoTo PROPAGATE_ERROR + + Dim word1 As New API_WordWrapper: Call word1.DisableMessages + Dim word2 As New API_WordWrapper: Call word2.DisableMessages + Dim sFile$: sFile = Dev_GetTestFolder & "\" & TEST_WORD_DOCUMENT + Dim doc1 As Word.Document + Dim doc2 As Word.Document + Dim app1 As Word.Application + Dim sText$: sText = "TestWord" + + Set app1 = word_.Application + + Call Dev_NewCase("Empty wrapper") + Call Dev_ExpectFalse(word1.ReleaseDocument) + + Call Dev_NewCase("No open document") + Call Dev_ExpectFalse(word_.ReleaseDocument(bCloseApplication:=False), "Do not force close application") + Call Dev_ExpectNotNothing(word_.Application, "Do not reset application") + Call Dev_ExpectObjectValid(app1, "Do not close application") + Call Dev_ExpectFalse(word_.ReleaseDocument(bCloseApplication:=True), "Force close application") + Call Dev_ExpectNotNothing(word_.Application, "Do not reset application") + Call Dev_ExpectObjectValid(app1, "Do not close application") + + Call Dev_NewCase("Release owned document - newly created no save changes") + Set doc1 = word_.NewDocument + doc1.Words.First = sText + Call Dev_ExpectTrue(word_.ReleaseDocument(bCloseApplication:=False, bSaveChanges:=False)) + Call Dev_ExpectNotNothing(word_.Application, "Do not reset application") + Call Dev_ExpectNothing(word_.Document, "Reset document") + Call Dev_ExpectObjectValid(app1, "Do not close application") + Call Dev_ExpectObjectInvalid(doc1, "Close document") + + Call Dev_NewCase("Release owned document - newly created save changes") + Set doc1 = word_.NewDocument + doc1.Words.First = sText + Call Dev_ExpectTrue(word_.ReleaseDocument(bCloseApplication:=False, bSaveChanges:=True)) + Call Dev_ExpectNotNothing(word_.Application, "Do not reset application") + Call Dev_ExpectNothing(word_.Document, "Reset document") + Call Dev_ExpectObjectInvalid(doc1, "Close document") + Call Dev_ExpectObjectValid(app1, "Do not close application") + + Call Dev_NewCase("Release owned document - dont save changes") + Set doc1 = word_.NewDocument + Call word_.SaveAs(sFile) + word_.Document.Words.First = sText + Call Dev_ExpectTrue(word_.ReleaseDocument(bCloseApplication:=False, bSaveChanges:=False)) + Call Dev_ExpectNothing(word_.Document, "Reset document") + Call Dev_ExpectNotNothing(word_.Application, "Do not reset application") + Call Dev_ExpectObjectInvalid(doc1, "Close document") + Call Dev_ExpectObjectValid(app1, "Do not close application") + Call word_.OpenDocument(sFile, bReadOnly:=False) + Call Dev_ExpectNE(sText, word_.Document.Words.First, "Do not save changes") + Call word_.ReleaseDocument(bCloseApplication:=False) + + Call Dev_NewCase("Release owned document - save changes") + Set doc1 = word_.NewDocument + Call word_.SaveAs(sFile) + word_.Document.Words.First = sText + Call Dev_ExpectTrue(word_.ReleaseDocument(bCloseApplication:=False, bSaveChanges:=True)) + Call Dev_ExpectNothing(word_.Document, "Reset document") + Call Dev_ExpectNotNothing(word_.Application, "Do not reset application") + Call Dev_ExpectObjectInvalid(doc1, "Close document") + Call Dev_ExpectObjectValid(app1, "Do not close application") + Call word_.OpenDocument(sFile, bReadOnly:=False) + Call Dev_ExpectEQ(sText, word_.Document.Words.First, "Save changes") + Call word_.ReleaseDocument(bCloseApplication:=False) + + Call Dev_NewCase("Release owned document - close app") + Set doc1 = word_.NewDocument + Call Dev_ExpectTrue(word_.ReleaseDocument(bCloseApplication:=True, bSaveChanges:=True)) + Call Dev_ExpectNothing(word_.Document, "Reset document") + Call Dev_ExpectNothing(word_.Application, "Reset application") + Call Dev_ExpectObjectInvalid(doc1, "Close document") + Call Dev_ExpectObjectInvalid(app1, "Close application") + + Call Dev_NewCase("Release non-owned document") + Set doc1 = word1.NewDocument + Call word2.SetDocument(doc1, bOwnership:=False) + Call Dev_ExpectTrue(word2.ReleaseDocument) + Call Dev_ExpectNothing(word2.Document, "Reset document") + Call Dev_ExpectNotNothing(word1.Document, "Do not reset owner") + Call Dev_ExpectObjectValid(doc1, "Do not close document") + Call word1.ReleaseDocument + + Call Dev_NewCase("Release owned dangling document") + Set doc1 = word1.NewDocument + Call word2.SetDocument(doc1, bOwnership:=True) + Call word1.ReleaseDocument + Call Dev_ExpectObjectInvalid(doc1, "Close document") + Call Dev_ExpectFalse(word2.ReleaseDocument) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_PauseUI() + On Error GoTo PROPAGATE_ERROR + + Dim word1 As New API_WordWrapper + Dim doc As Word.Document + + Call Dev_NewCase("Empty wrapper") + On Error Resume Next + Call word1.PauseUI + Call Dev_ExpectNoError + Call Dev_ExpectFalse(word1.IsUIPaused, "Pause") + On Error Resume Next + Call word1.ResumeUI + Call Dev_ExpectNoError + Call Dev_ExpectFalse(word1.IsUIPaused, "Resume") + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("No document") + On Error Resume Next + Call word_.PauseUI + Call Dev_ExpectNoError + Call Dev_ExpectFalse(word_.IsUIPaused) + On Error Resume Next + Call word_.ResumeUI + Call Dev_ExpectNoError + Call Dev_ExpectFalse(word_.IsUIPaused) + On Error GoTo PROPAGATE_ERROR + + Call Dev_NewCase("Owned document") + Set doc = word_.NewDocument + Call Dev_ExpectTrue(word_.Application.ScreenUpdating) + Call word_.PauseUI + Call Dev_ExpectTrue(word_.IsUIPaused, "Pause") + Call Dev_ExpectFalse(word_.Application.ScreenUpdating, "Pause") + Call word_.ResumeUI + Call Dev_ExpectFalse(word_.IsUIPaused, "Resume") + Call Dev_ExpectTrue(word_.Application.ScreenUpdating, "Resume") + Call word_.ReleaseDocument + + Call Dev_NewCase("Non-owned document") + Set doc = word1.NewDocument + Call word_.SetDocument(doc, bOwnership:=False) + Call word_.PauseUI + Call Dev_ExpectTrue(word_.IsUIPaused, "Pause") + Call Dev_ExpectFalse(word1.IsUIPaused, "Do not pause owner") + Call Dev_ExpectFalse(word_.Application.ScreenUpdating, "Pause") + Call word_.ResumeUI + Call Dev_ExpectFalse(word_.IsUIPaused, "Resume") + Call Dev_ExpectTrue(word_.Application.ScreenUpdating, "Resume") + + Call Dev_NewCase("Overlapped pause different wrappers") + Call word_.PauseUI + Call word1.PauseUI + Call Dev_ExpectTrue(word_.IsUIPaused, "Pause") + Call Dev_ExpectTrue(word1.IsUIPaused, "Pause") + Call Dev_ExpectFalse(word_.Application.ScreenUpdating, "Pause") + Call word1.ResumeUI + Call Dev_ExpectFalse(word1.IsUIPaused, "Resume inner") + Call Dev_ExpectTrue(word_.IsUIPaused, "Do not resume outer") + Call Dev_ExpectFalse(word_.Application.ScreenUpdating, "Do not enable UI") + Call word_.ResumeUI + Call Dev_ExpectFalse(word1.IsUIPaused, "Resume inner") + Call Dev_ExpectFalse(word_.IsUIPaused, "Resume outer") + Call Dev_ExpectTrue(word_.Application.ScreenUpdating, "Enable UI") + + Call word1.ReleaseDocument + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_SaveAs() + On Error GoTo PROPAGATE_ERROR + + Dim sFile$: sFile = Dev_GetTestFolder & "\" & TEST_WORD_DOCUMENT + Dim sTemplate$: sTemplate = Dev_GetTestFolder & "\" & "testFile" + Dim sText$: sText = "TestWord" + Dim sTarget$ + Call fso_.CopyFile(Dev_GetArtifactFolder & "\" & TEST_WORD_DOCUMENT, sFile) + Dim oReader As New API_WordWrapper + Call oReader.CreateApplication + Call oReader.DisableMessages + + Call Dev_NewCase("No document") + On Error Resume Next + Call Dev_ExpectFalse(word_.SaveAs(sTemplate & ".docx")) + Call Dev_ExpectNoError + On Error Resume Next + + Call Dev_AssertTrue(word_.OpenDocument(sFile)) + Call Dev_ExpectFalse(word_.SaveAs("invalid!?"), "Invalid filename") + + Call Dev_NewCase("Save as docx") + sTarget = sTemplate & ".docx" + Call Dev_ExpectTrue(word_.SaveAs(sTarget)) + Call Dev_AssertTrue(fso_.FileExists(sTarget), "File created") + Call Dev_ExpectTrue(oReader.OpenDocument(sTarget, bReadOnly:=True), "Open saved document") + Call Dev_ExpectEQ(sText, oReader.Document.Words.First, "Validate contents") + Call oReader.ReleaseDocument(bCloseApplication:=False) + + Call Dev_NewCase("Save as docm") + sTarget = sTemplate & ".docm" + Call Dev_ExpectTrue(word_.SaveAs(sTarget)) + Call Dev_AssertTrue(fso_.FileExists(sTarget), "File created") + Call Dev_ExpectTrue(oReader.OpenDocument(sTarget, bReadOnly:=True), "Open saved document") + Call Dev_ExpectEQ(sText, oReader.Document.Words.First, "Validate contents") + Call oReader.ReleaseDocument(bCloseApplication:=False) + + Call Dev_NewCase("Save as dotx") + sTarget = sTemplate & ".dotx" + Call Dev_ExpectTrue(word_.SaveAs(sTarget)) + Call Dev_AssertTrue(fso_.FileExists(sTarget), "File created") + Call Dev_ExpectTrue(oReader.OpenDocument(sTarget, bReadOnly:=True), "Open saved document") + Call Dev_ExpectEQ(sText, oReader.Document.Words.First, "Validate contents") + Call oReader.ReleaseDocument(bCloseApplication:=False) + + Call Dev_NewCase("Save as dotm") + sTarget = sTemplate & ".dotm" + Call Dev_ExpectTrue(word_.SaveAs(sTarget)) + Call Dev_AssertTrue(fso_.FileExists(sTarget), "File created") + Call Dev_ExpectTrue(oReader.OpenDocument(sTarget, bReadOnly:=True), "Open saved document") + Call Dev_ExpectEQ(sText, oReader.Document.Words.First, "Validate contents") + Call oReader.ReleaseDocument(bCloseApplication:=False) + + Call Dev_NewCase("Overwrite") + sTarget = sTemplate & ".docx" + sText = "test2" + word_.Document.Words.First = sText + Call Dev_ExpectTrue(word_.SaveAs(sTarget)) + Call Dev_AssertTrue(fso_.FileExists(sTarget), "File created") + Call Dev_ExpectTrue(oReader.OpenDocument(sTarget, bReadOnly:=True), "Open saved document") + Call Dev_ExpectEQ(sText, oReader.Document.Words.First, "Validate contents") + Call oReader.ReleaseDocument(bCloseApplication:=False) + + Call oReader.ReleaseApplication + Call word_.ReleaseDocument + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function diff --git a/src/test/s_XLWrapper.cls b/src/test/s_XLWrapper.cls new file mode 100644 index 0000000..016f570 --- /dev/null +++ b/src/test/s_XLWrapper.cls @@ -0,0 +1,100 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "s_XLWrapper" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +' Note: Testing only functions different from API_WordWrapper +' TODO: test OpenDocument with ignore folder + +Private xl_ As API_XLWrapper +Private fso_ As Scripting.FileSystemObject + +Public Function Setup() + ' Mandatory setup function + Set xl_ = New API_XLWrapper + Call xl_.DisableMessages + Call xl_.CreateApplication + Set fso_ = New Scripting.FileSystemObject + Call EnsureFolderExists(Dev_GetTestFolder) +End Function + +Public Function Teardown() + ' Mandatory teardown function + Call fso_.DeleteFolder(Dev_GetTestFolder) + Call xl_.ReleaseApplication + Set xl_ = Nothing +End Function + +Public Function t_SaveAs() + On Error GoTo PROPAGATE_ERROR + + Dim sTemplate$: sTemplate = Dev_GetTestFolder & "\" & "testFile" + Dim sText$: sText = "TestWord" + Dim sTarget$ + Dim oReader As New API_XLWrapper + Call oReader.CreateApplication + Call oReader.DisableMessages + + Call Dev_NewCase("No document") + On Error Resume Next + Call Dev_ExpectFalse(xl_.SaveAs(sTemplate & ".xlsx")) + Call Dev_ExpectNoError + On Error Resume Next + + Call xl_.NewDocument + xl_.Document.Worksheets(1).Cells(1, 1) = sText + Call Dev_ExpectFalse(xl_.SaveAs("invalid!?"), "Invalid filename") + + Call Dev_NewCase("Save as xlsx") + sTarget = sTemplate & ".xlsx" + Call Dev_ExpectTrue(xl_.SaveAs(sTarget)) + Call Dev_AssertTrue(fso_.FileExists(sTarget), "File created") + Call Dev_ExpectTrue(oReader.OpenDocument(sTarget, bReadOnly:=True), "Open saved document") + Call Dev_ExpectEQ(sText, oReader.Document.Worksheets(1).Cells(1, 1).Text, "Validate contents") + Call oReader.ReleaseDocument(bCloseApplication:=False) + + Call Dev_NewCase("Save as xlsm") + sTarget = sTemplate & ".xlsm" + Call Dev_ExpectTrue(xl_.SaveAs(sTarget)) + Call Dev_AssertTrue(fso_.FileExists(sTarget), "File created") + Call Dev_ExpectTrue(oReader.OpenDocument(sTarget, bReadOnly:=True), "Open saved document") + Call Dev_ExpectEQ(sText, oReader.Document.Worksheets(1).Cells(1, 1).Text, "Validate contents") + Call oReader.ReleaseDocument(bCloseApplication:=False) + + Call Dev_NewCase("Save as xltx") + sTarget = sTemplate & ".xltx" + Call Dev_ExpectTrue(xl_.SaveAs(sTarget)) + Call Dev_AssertTrue(fso_.FileExists(sTarget), "File created") + Call Dev_ExpectTrue(oReader.OpenDocument(sTarget, bReadOnly:=True), "Open saved document") + Call Dev_ExpectEQ(sText, oReader.Document.Worksheets(1).Cells(1, 1).Text, "Validate contents") + Call oReader.ReleaseDocument(bCloseApplication:=False) + + Call Dev_NewCase("Save as xltm") + sTarget = sTemplate & ".xltm" + Call Dev_ExpectTrue(xl_.SaveAs(sTarget)) + Call Dev_AssertTrue(fso_.FileExists(sTarget), "File created") + Call Dev_ExpectTrue(oReader.OpenDocument(sTarget, bReadOnly:=True), "Open saved document") + Call Dev_ExpectEQ(sText, oReader.Document.Worksheets(1).Cells(1, 1).Text, "Validate contents") + Call oReader.ReleaseDocument(bCloseApplication:=False) + + Call Dev_NewCase("Save as xlam") + sTarget = sTemplate & ".xlam" + Call Dev_ExpectTrue(xl_.SaveAs(sTarget)) + Call Dev_AssertTrue(fso_.FileExists(sTarget), "File created") + Call Dev_ExpectTrue(oReader.OpenDocument(sTarget, bReadOnly:=True), "Open saved document") + Call Dev_ExpectEQ(sText, oReader.Document.Worksheets(1).Cells(1, 1).Text, "Validate contents") + Call oReader.ReleaseDocument(bCloseApplication:=False) + + Call xl_.ReleaseDocument + Call oReader.ReleaseApplication + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function diff --git a/src/visio/Declarations.bas b/src/visio/Declarations.bas new file mode 100644 index 0000000..645a9cf --- /dev/null +++ b/src/visio/Declarations.bas @@ -0,0 +1,8 @@ +Attribute VB_Name = "Declarations" +Option Private Module +Option Explicit + +Public Const MASTER_ELEMENT = "SimpleElement" +Public Const MASTER_LIST = "ListContainer" +Public Const MASTER_CONTAINER = "MembersContainer" +Public Const MASTER_CONNECTOR = "Connector" diff --git a/src/visio/DevHelper.bas b/src/visio/DevHelper.bas new file mode 100644 index 0000000..8096c8b --- /dev/null +++ b/src/visio/DevHelper.bas @@ -0,0 +1,25 @@ +Attribute VB_Name = "DevHelper" +Option Explicit + +Public Function Dev_PrepareSkeleton() + ' Do nothing + Call ClearAll +End Function + +Public Sub Dev_ManualRunTest() + Dim sSuite$: sSuite = "s_VsoUtilities" + Dim sTest$: sTest = "t_VsoIsMovable" + Dim sMsg$: sMsg = Dev_RunTestDebug(sSuite, sTest) + Debug.Print sMsg + Call MsgBox(sMsg) +End Sub + +Public Function Dev_GetTestSuite(sName$) As Object + Select Case sName + Case "s_UndoWrapper": Set Dev_GetTestSuite = New s_UndoWrapper + Case "s_Factorizator": Set Dev_GetTestSuite = New s_Factorizator + Case "s_VsoExtension": Set Dev_GetTestSuite = New s_VsoExtension + Case "s_VsoGraph": Set Dev_GetTestSuite = New s_VsoGraph + Case "s_VsoUtilities": Set Dev_GetTestSuite = New s_VsoUtilities + End Select +End Function diff --git a/src/visio/Main.bas b/src/visio/Main.bas new file mode 100644 index 0000000..e471a10 --- /dev/null +++ b/src/visio/Main.bas @@ -0,0 +1,7 @@ +Attribute VB_Name = "Main" +Option Private Module +Option Explicit + +Public Sub ClearAll() + Call VsoClearPage(ActivePage) +End Sub diff --git a/src/visio/MainImpl.bas b/src/visio/MainImpl.bas new file mode 100644 index 0000000..50e4abe --- /dev/null +++ b/src/visio/MainImpl.bas @@ -0,0 +1,4 @@ +Attribute VB_Name = "MainImpl" +Option Private Module +Option Explicit + diff --git a/src/word/Declarations.bas b/src/word/Declarations.bas new file mode 100644 index 0000000..c8583e3 --- /dev/null +++ b/src/word/Declarations.bas @@ -0,0 +1,4 @@ +Attribute VB_Name = "Declarations" +Option Private Module +Option Explicit + diff --git a/src/word/DevHelper.bas b/src/word/DevHelper.bas new file mode 100644 index 0000000..eeb3620 --- /dev/null +++ b/src/word/DevHelper.bas @@ -0,0 +1,20 @@ +Attribute VB_Name = "DevHelper" +Option Explicit + +Public Function Dev_PrepareSkeleton() + ' Do nothing +End Function + +Public Sub Dev_ManualRunTest() + Dim sSuite$: sSuite = "s_TextEdit" + Dim sTest$: sTest = "t_AdjustRange" + Dim sMsg$: sMsg = Dev_RunTestDebug(sSuite, sTest) + Debug.Print sMsg + Call MsgBox(sMsg) +End Sub + +Public Function Dev_GetTestSuite(sName$) As Object + Select Case sName + Case "s_TextEdit": Set Dev_GetTestSuite = New s_TextEdit + End Select +End Function diff --git a/src/word/Main.bas b/src/word/Main.bas new file mode 100644 index 0000000..e50484d --- /dev/null +++ b/src/word/Main.bas @@ -0,0 +1,3 @@ +Attribute VB_Name = "Main" +Option Private Module +Option Explicit diff --git a/src/word/MainImpl.bas b/src/word/MainImpl.bas new file mode 100644 index 0000000..50e4abe --- /dev/null +++ b/src/word/MainImpl.bas @@ -0,0 +1,4 @@ +Attribute VB_Name = "MainImpl" +Option Private Module +Option Explicit + diff --git a/test/TestInvalid.docx b/test/TestInvalid.docx new file mode 100644 index 0000000000000000000000000000000000000000..b5a22d3fb6ada5568c9095ce9c8662823746f095 GIT binary patch literal 8381 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

SlLeJ4>z%d1ept{3sWY7759 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 Hb@u-Ny6?QS literal 0 HcmV?d00001 diff --git a/test/testTemplate.dotx b/test/testTemplate.dotx new file mode 100644 index 0000000000000000000000000000000000000000..81620106ee4e552e6d508a62f81dddf78edad475 GIT binary patch literal 21524 zcmeFZbC7M#k~iEqZQJ%~+qP}nwr$(CZM#qRY1_8lr}_2s+?jdreD}s1@%}xtA~IvI z$jZI4*8XKyR#n!PlLY>O3;+rM4gdf^0I&&VN0A5!06-500DuGl4x}k)YvW{WNETsexGXYu5K4tBV77`R9u5BoOfoL;Kh&gs zww;@tG~I%BEB)hvFt4(55$XLG?&7(u#0XLbtNSJO<3H2D`^_lg&b+P`a+9W52*?gQ z$fTXcSQq+Uwt^}g*z_tr3ulF@@v*|gY6le1bYIHa?i@_S)A3qY2TvyyB+DL?HN$iS zB$ym69e!Y){D{-c-;D9a3(X>Vc$&bh5oe6xDcV|PYc@XwLttKDzq?$fFON zL_Pw#mNvN5fdFJqGI-?v>7urd9a%K>NLYY^_ho;v30u36Z8ry6f%NP3;-24`n_SMb z;m_*;*84XMzrKJ0>%;$p9rvHQUX{>q zF-Ql^cOCcWO#1RHB%Qb;|lkTo9R+cvZ{r8CWul^CZXBhSL?Hk}w000QzFU8f?!HCw#*3jAd zd%*i^q$^6W-Us+OGUn3r^uWmi-%zg{ zbOf}1{PO<(yc~{~E5n6{lctnTZUWomnvFnN7S>3Z&d%#wbwxTQX09+qj0Td)Zd#I6 z`sY|Sc9a~|X<9vB)Q?=riR$#Xcry)A5VAA|*;$UM5+sSsYYt@0kWH$5NQ}V9m}9-A zWifIR1<1ZP$*@0p>By`iuLjjGga8;iJ|+@{M#YIfRzchuWa$okTL5sAlUO|VDEr!x5|Ch|)A3~v)_b~u z?Zlm0Chw$j{ZB(+LX3vj0fgb53G8@#(&VUKL>2Mv#k%VYaWO*b$5cH18*1JU2zJL&vu7$(WOfUSq(;J3z)p$`vw|_ z7p)yBn^3!~kF89*4AC@kiRgiuKNo7wWx%}`rJmMUHZ9nGI)JDsBSL0!xmcob%(H8f zwZLA_qM5UBjyDEWGfMN<<7og`sEnvoR*PODxqM>%J12n_Imu)Eb{{Da003+NaKOKv z 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 + \ No newline at end of file diff --git a/ui/customUI.xml b/ui/customUI.xml new file mode 100644 index 0000000..aa32755 --- /dev/null +++ b/ui/customUI.xml @@ -0,0 +1,199 @@ + + + + + + +