diff options
Diffstat (limited to 'desktop/qa')
33 files changed, 4326 insertions, 0 deletions
diff --git a/desktop/qa/data/2slides.odp b/desktop/qa/data/2slides.odp Binary files differnew file mode 100644 index 0000000000..0e3f8758ff --- /dev/null +++ b/desktop/qa/data/2slides.odp diff --git a/desktop/qa/data/3page.odg b/desktop/qa/data/3page.odg Binary files differnew file mode 100644 index 0000000000..1fad913e04 --- /dev/null +++ b/desktop/qa/data/3page.odg diff --git a/desktop/qa/data/BlankDrawDocument.odg b/desktop/qa/data/BlankDrawDocument.odg Binary files differnew file mode 100644 index 0000000000..19ae49d63b --- /dev/null +++ b/desktop/qa/data/BlankDrawDocument.odg diff --git a/desktop/qa/data/SearchIndexResultShapeTest.odt b/desktop/qa/data/SearchIndexResultShapeTest.odt Binary files differnew file mode 100644 index 0000000000..4298eb8ad0 --- /dev/null +++ b/desktop/qa/data/SearchIndexResultShapeTest.odt diff --git a/desktop/qa/data/SearchIndexResultTest.odt b/desktop/qa/data/SearchIndexResultTest.odt Binary files differnew file mode 100644 index 0000000000..58ed3a0f54 --- /dev/null +++ b/desktop/qa/data/SearchIndexResultTest.odt diff --git a/desktop/qa/data/ThemeDocument.docx b/desktop/qa/data/ThemeDocument.docx Binary files differnew file mode 100644 index 0000000000..4dbba883d9 --- /dev/null +++ b/desktop/qa/data/ThemeDocument.docx diff --git a/desktop/qa/data/blank_presentation.odp b/desktop/qa/data/blank_presentation.odp Binary files differnew file mode 100644 index 0000000000..a7d57a48e4 --- /dev/null +++ b/desktop/qa/data/blank_presentation.odp diff --git a/desktop/qa/data/blank_text.docx b/desktop/qa/data/blank_text.docx Binary files differnew file mode 100644 index 0000000000..028a35b6ca --- /dev/null +++ b/desktop/qa/data/blank_text.docx diff --git a/desktop/qa/data/blank_text.odt b/desktop/qa/data/blank_text.odt Binary files differnew file mode 100644 index 0000000000..00b92d785a --- /dev/null +++ b/desktop/qa/data/blank_text.odt diff --git a/desktop/qa/data/certificate.der b/desktop/qa/data/certificate.der Binary files differnew file mode 100644 index 0000000000..10e3ade13e --- /dev/null +++ b/desktop/qa/data/certificate.der diff --git a/desktop/qa/data/certificatePrivateKey.der b/desktop/qa/data/certificatePrivateKey.der Binary files differnew file mode 100644 index 0000000000..7a5599c825 --- /dev/null +++ b/desktop/qa/data/certificatePrivateKey.der diff --git a/desktop/qa/data/comments.odt b/desktop/qa/data/comments.odt Binary files differnew file mode 100644 index 0000000000..1bcdcc0385 --- /dev/null +++ b/desktop/qa/data/comments.odt diff --git a/desktop/qa/data/hidden-row.ods b/desktop/qa/data/hidden-row.ods Binary files differnew file mode 100644 index 0000000000..25fe89865d --- /dev/null +++ b/desktop/qa/data/hidden-row.ods diff --git a/desktop/qa/data/intermediateRootCA.der b/desktop/qa/data/intermediateRootCA.der Binary files differnew file mode 100644 index 0000000000..9adf7f82e5 --- /dev/null +++ b/desktop/qa/data/intermediateRootCA.der diff --git a/desktop/qa/data/objects.odt b/desktop/qa/data/objects.odt Binary files differnew file mode 100644 index 0000000000..45c2b39cc1 --- /dev/null +++ b/desktop/qa/data/objects.odt diff --git a/desktop/qa/data/paste.jpg b/desktop/qa/data/paste.jpg Binary files differnew file mode 100644 index 0000000000..ca9183e9d3 --- /dev/null +++ b/desktop/qa/data/paste.jpg diff --git a/desktop/qa/data/rootCA.der b/desktop/qa/data/rootCA.der Binary files differnew file mode 100644 index 0000000000..30fc66e26f --- /dev/null +++ b/desktop/qa/data/rootCA.der diff --git a/desktop/qa/data/search.ods b/desktop/qa/data/search.ods Binary files differnew file mode 100644 index 0000000000..ea1d731538 --- /dev/null +++ b/desktop/qa/data/search.ods diff --git a/desktop/qa/data/sheet_with_image.ods b/desktop/qa/data/sheet_with_image.ods Binary files differnew file mode 100644 index 0000000000..00c0019cb8 --- /dev/null +++ b/desktop/qa/data/sheet_with_image.ods diff --git a/desktop/qa/data/sheets.ods b/desktop/qa/data/sheets.ods Binary files differnew file mode 100644 index 0000000000..3f43fa3a3d --- /dev/null +++ b/desktop/qa/data/sheets.ods diff --git a/desktop/qa/data/signed.odt b/desktop/qa/data/signed.odt Binary files differnew file mode 100644 index 0000000000..49bd9dd240 --- /dev/null +++ b/desktop/qa/data/signed.odt diff --git a/desktop/qa/data/table-selection.odt b/desktop/qa/data/table-selection.odt Binary files differnew file mode 100644 index 0000000000..c19f8c79fc --- /dev/null +++ b/desktop/qa/data/table-selection.odt diff --git a/desktop/qa/data/test-PK-signing.pem b/desktop/qa/data/test-PK-signing.pem new file mode 100644 index 0000000000..eabbaae18c --- /dev/null +++ b/desktop/qa/data/test-PK-signing.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC/x1dyX2PgJs6o +e/BcBT5XTJXtipme29mtI/FmTq5vuAFopqvAEg084hNM7Q++wKyKG8F0ABF2pUOY +vo6Pq6rXQqMbxUaO47NNefh4v/f6hBoMfSYmXtEPji8NjIcIrQ3U8yTWquaHi13h +coG/cVf16Doem5AxUdQ6a9e/jA1VU2MmSZxthkABKPWdw1pfFJDLWhFrCjhePKrB +xw2ArmNx66fdzpi5XK0fC2TbHD2NVc8cbYOzF4h3knGZo2sq4/MBevyVhzXZNn2P +jf3PbOxp1O8CLx86MuDTLDkSVjmP0yXBf6rLFMWtEfv1Jmf3I61meIuOcw6wvXRK +zPnAKpLRAgMBAAECggEAFQU1JooiGQg9OpEV0ArwbFfZTxy+qH+Rz70oJn3qohWE +bK4SwTi5TrpmQdZHatxqN7EXIS8out0ebaLlXrCtnG6SEOcsoVjVPGFpb1ggnCAt +TWp3lgO/3SPz2wOo1rXxKtguaivNR39qc4g+LyJYm9GFHU8RHPbKe6TPvw+2HmtB +Qr2dfwgN+JJ/lBTIE0lUVuGcBcCRxbM6aG3WVpVrWcsGwRekcuuy9xrv+6fd1p2E +7zdM0/18+tnCWF9kCO6ot+spKJGTKqiuaKrSB54gFnDEgjQKIW7lUuOcXKI8vOZb +yO3owLE4mlNbE9sv8gPKoMXf2d6wzMRA6wBdaqOlKQKBgQD1XhyNDTpCoqcO82KY +YCJOZ6d1jH27XhHplfmrYDlQuWiuJ/b3ckq5Pqw5J5pRO19tvb4RSonYtmOcLajR +CkQtHapeH8mUzGsE/tgauB4KoZ1sVhzsag0Ill44P5/5oBlzhRrvJ7L80qlHn8xC +a4Tzk5t0tWbCS6K3/rqvil0hXwKBgQDIFsMoujc8hW5jyC6rO41elWEDVmPs5P+5 +RYxH5+uVeByyz2R86CAFmHUn/1nD47KKouNhwU4Anf5lA8JHh9rNVF07a2dAfH1o +yfkBGz8d3xb5hq3ahVGg9WyMRyfczGGA19uJrQ19dY4G4B4FPPz0J6oOnNkTFaQ9 +Bks/k/fJzwKBgGSBqVZJzcyPzbh9D6z06/iL0vd+ld4TGWlCKqP9ZVzgpbV431va +sCsTNf6vbzHJDTzplRqGGtLvWvwVY+pEt0p3tVqa0LqnxUqljSXct0mJi+9dkrlw +c2hKF8wYm9Hnt6UvJ6pA67tOG1MgbM3kNvCDTRFQYQhDbSLLL/NJzP4nAoGAJqhv +MFE6FtFY0KJ+kcrBt4J46eIpED32Ql9ziPkABTLdqJZ1PcTDWxFnoUCuoTA+8JYk +BGEKpwfffLjLMnLHDWC9WpuXqVfkCvjqyRHwkd7mW3Nv54ZWjRidzkR5KSm7tN7/ +pYvvzUuHE0D9y9lKrglzy7r2Hb/SqY+rvi7icvUCgYADV2kkky++OmCVLkEg3WUf +SJkF6jUAVMqlMdjTbySEfCJbxpVwRAiUWWlDD07c5HCBEASi6/NSn06MDb9Fvxo0 +a3m24Aa2c+K4ENj+bj453gdxhtvpeyfSRK+gBEP64iBG92UFJjcwHz5kFCzppPuP +p+ZtA6JAnV6QPT1EixAOCA== +-----END PRIVATE KEY----- diff --git a/desktop/qa/data/test-cert-chain-1.pem b/desktop/qa/data/test-cert-chain-1.pem new file mode 100644 index 0000000000..3a3407caf6 --- /dev/null +++ b/desktop/qa/data/test-cert-chain-1.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIID+zCCAuOgAwIBAgIUaS0j9S5ZbREDcPMsLlfFE0+oUE0wDQYJKoZIhvcNAQEL +BQAwajELMAkGA1UEBhMCQ0gxDDAKBgNVBAgMA1p1ZzEMMAoGA1UEBwwDWnVnMRMw +EQYDVQQKDApWZXJlaWduIEFHMRUwEwYDVQQLDAxCdXNzaW5lcyBEZXAxEzARBgNV +BAMMCnZlcmVpZ24tY2EwHhcNMTgxMTI0MDAwMDAwWhcNMjMxMjE4MjM1OTU5WjCB +jzELMAkGA1UEBhMCQ0gxDDAKBgNVBAgTA1p1ZzEMMAoGA1UEBxMDWnVnMRMwEQYD +VQQKEwpWZXJlaWduIEFHMRUwEwYDVQQLEwxCdXNpbmVzcyBEZXAxODA2BgNVBAMT +L2YxZmUxZGJhLWZiMDUtNDk5Ni04Zjk1LWE4MTQyMjE5OGRiOS1zZXJ2ZXJzaWRl +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzE7WuIkjvhWceJGwLoYN +2+huR47jlqqb0ez5VMGuZqK8qn1uf0PB6j/OW9B0cdBs65nBTL2CrkZncKov5g/i +yZADuiezqBtEUr6J74oRBVPWV+e9iNr3M2FMtG/lkpYDpkvNziibMLE4kyhgbbRd +Q0OHz55mk4Wn3CYAA/a7zhMgCjvT+wPuLXJjLje+2bB4rMv/USlnTN3DINx4i/Vt +klNNtK5NSaxlkYf1QuyxXeHEJUufVuFY7sG4xZBhh75yUF7Z7836Oi1++DNeuWc/ +YOmsrfu1lqDfYNjb5IpOMz9x2HtmG6V3ETeKQX8GIs34qhG6zA9Up3JkSQsd9qTP +1QIDAQABo3MwcTAOBgNVHQ8BAf8EBAMCAbYwDwYDVR0TAQH/BAUwAwEB/zAfBgNV +HSMEGDAWgBQlN+K7lesKXsDZYQUu4zkqtNBwrjAaBgNVHREEEzARgg93d3cudmVy +ZWlnbi5jb20wEQYDVR0gBAowCDAGBgRVHSAAMA0GCSqGSIb3DQEBCwUAA4IBAQAx +86wXlw779hT3Jad4EfuozQycLw4lXLMiGdHuLdRGNdg6faK2p5qaJXFEd13pE/Qn +AI2z5SpuYr2G3NYJsT3deXi8Yh58AsBSF5UY61xCgITXOW2NaB2gb6L7sL8Uau8i +BE5yn0r0V6wD3gxG7yJRNPgH7ksELfN1b++BHdvkcodC1H4vHzl0mAJvYaNlPBhd +LDnGU3GKhZ1r2pF+eXIW7n3BbAi/a7226Or4eTWEjc0footKJeJLfsG4HOcSj+Bi +LKAeftebGRRsZzuL6tLTzoSiRsEn6Y6GOdxRM21Uu1rvtWmfDyNRuCcKZU8Secqy +0s1uSGkNr+6wgcWqA+VQ +-----END CERTIFICATE----- diff --git a/desktop/qa/data/test-cert-chain-2.pem b/desktop/qa/data/test-cert-chain-2.pem new file mode 100644 index 0000000000..a31db3f657 --- /dev/null +++ b/desktop/qa/data/test-cert-chain-2.pem @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEdjCCA16gAwIBAgIEW7HqGDANBgkqhkiG9w0BAQsFADBqMQswCQYDVQQGEwJD +SDEMMAoGA1UECAwDWnVnMQwwCgYDVQQHDANadWcxEzARBgNVBAoMClZlcmVpZ24g +QUcxFTATBgNVBAsMDEJ1c3NpbmVzIERlcDETMBEGA1UEAwwKdmVyZWlnbi1jYTAe +Fw0xODEwMDEwOTM0MTZaFw0xOTEwMDEwOTM0MTZaMGoxCzAJBgNVBAYTAkNIMQww +CgYDVQQIDANadWcxDDAKBgNVBAcMA1p1ZzETMBEGA1UECgwKVmVyZWlnbiBBRzEV +MBMGA1UECwwMQnVzc2luZXMgRGVwMRMwEQYDVQQDDAp2ZXJlaWduLWNhMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsfpPjrblQuxrHiSLAAyyDgRd66gY +PRo7lgKZH5NYcBO9VhJNwnvV+fBIVeJI49b+a12TPHjRzJYrkaBAcUxMM8FkZ01A +mv6JSG4o2ZXV+GWpnWzEJzt9ZXmNZ1MSUlqIGzVZ/eUlXIj4gy57+SZoJURcQGhs +jpoRgUpYnFsDJk2x77jiOa5ym/N+8HKsOabASMU6VkbIFvUqf62RXWpnQlOhFjGo +0jvheRGBWbaYKHM3/d+u78w4tmvHqGVDDbsuOluZ39p2jCic9S7CnDkauZB0Afd/ +xgQ0CglpAgY8g4cfMl2zwRmm616PtutqjcE/NoA2JEVN5vP9QZsuXeRpJwIDAQAB +o4IBIjCCAR4wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwLwYDVR0R +BCgwJoIPd3d3LnZlcmVpZ24uY29tgRNjb250YWN0QHZlcmVpZ24uY29tMBEGA1Ud +IAQKMAgwBgYEVR0gADCBlwYDVR0jBIGPMIGMgBQlN+K7lesKXsDZYQUu4zkqtNBw +rqFupGwwajELMAkGA1UEBhMCQ0gxDDAKBgNVBAgMA1p1ZzEMMAoGA1UEBwwDWnVn +MRMwEQYDVQQKDApWZXJlaWduIEFHMRUwEwYDVQQLDAxCdXNzaW5lcyBEZXAxEzAR +BgNVBAMMCnZlcmVpZ24tY2GCBFux6hgwHQYDVR0OBBYEFCU34ruV6wpewNlhBS7j +OSq00HCuMA0GCSqGSIb3DQEBCwUAA4IBAQCG3tf8/tuCNJXby4B7decDNE6bff40 +1ybO17kzekrKj0IO2TatFIG+UDlxDfm2iydEQVoPuRTAgmJD1aq5g4C0ZLyUqmOg +75Dve6W9+zzxbdI711WKxH+uSj4mTRkFD4Tb7r3VZ1ZyZYnCOMIGB4/lqUK6Ok3a +2v8XaFcxHt5XhrQtgqd5bBGokQfwYPNVZW9FwXf/8cd59prEOnqlMbZJ7copgwYO +97abhpy2FUoRWtvDjDLLfdiFQhVY8meDcS/h5mw2aEugew8hnfSEaD5ZcbOf0ZQe +MOVxKbIzSeUDAFyRY6BPpGVPuJD6QAXRMW6KIWiGoF1taKp5G/nzbzJC +-----END CERTIFICATE----- diff --git a/desktop/qa/data/test-cert-chain-3.pem b/desktop/qa/data/test-cert-chain-3.pem new file mode 100644 index 0000000000..d02dbe0f6a --- /dev/null +++ b/desktop/qa/data/test-cert-chain-3.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID5zCCAs+gAwIBAgIU0Ar2t+Zp1ZC0Jx1An2HK+e+ghR0wDQYJKoZIhvcNAQEL +BQAwgY8xCzAJBgNVBAYTAkNIMQwwCgYDVQQIEwNadWcxDDAKBgNVBAcTA1p1ZzET +MBEGA1UEChMKVmVyZWlnbiBBRzEVMBMGA1UECxMMQnVzaW5lc3MgRGVwMTgwNgYD +VQQDEy9mMWZlMWRiYS1mYjA1LTQ5OTYtOGY5NS1hODE0MjIxOThkYjktc2VydmVy +c2lkZTAeFw0xODExMjMyMzAwMDBaFw0yMzExMjQyMjU5NTlaMIGGMYGDMDYGA1UE +AxMvZjFmZTFkYmEtZmIwNS00OTk2LThmOTUtYTgxNDIyMTk4ZGI5LXVzZXJkZXZp +Y2UwCQYDVQQGEwJDSDAKBgNVBAcTA1p1ZzAKBgNVBAgTA1p1ZzARBgNVBAoTClZl +cmVpZ24gQUcwEwYDVQQLEwxCdXNpbmVzcyBEZXAwggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQDFQ+2fHCdE9ksa/h0aMK/DHPdq3iGWHx6eFsG3yI7RSU/7 +IA/DPht7A4U/a4qw/8PqT9Df9CvNeURXDmIG1S4ZVn7oTlEua9Da5A+HByA6M4Vk +4meDo/tY6hE8NZy25Q3l0dZvH34eocRe0xhjGajoUo6vGfzzQ+pQPerM1VivraVf +EZva4b3rCN7XrZkzkBPjEZijLvlk1wKcG5R1teXlEMHiHKiIeECDXmBD406ngWlt +KzlH2bXZYkfdBSoYXW2eyM/z0kJcY4M/75re0YGMRUPK6Cp1C4F+jIIv8np09bKS +6lvr/niemJz0BPXapsLOWXsc0Gg9sPiMpwdNbjflAgMBAAGjQjBAMA4GA1UdDwEB +/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSdM4fK3wQY6/nXpgZL +5Nl5udSGGjANBgkqhkiG9w0BAQsFAAOCAQEAe/6URI5GLBee3OJo00iHXCYMxOoX +Bi2V6IC1zS6mQlRYDtzbUQAKJrxRVns2+wjeCqKetqSuZ00hM/ipCrnxwb/X0CKY +4az9Mf9BycYnkmGNKzzskefIlUciaThdc0Ju6RVlCgFXX8vP9iMO3iQ9ET7JS4jB +oRNFnMeyy4+HG1RwYivi1YjaPNkp7xd8Lqq56VULGY4dKZUieJceFXtYQZHuVgWo +woAulOZH0IYqTv16tHxLovWGJaWpoMwgWo/c8sk8CfYF5vv9SMxcFnWwNooCRtWI +HJzs/1zxXIFy9D49PcH0gwkPM66F8bPS8TMpHcyjRDF/TQFzu1JwVyFY0g== +-----END CERTIFICATE----- diff --git a/desktop/qa/data/test-cert-signing.pem b/desktop/qa/data/test-cert-signing.pem new file mode 100644 index 0000000000..8f5c788b53 --- /dev/null +++ b/desktop/qa/data/test-cert-signing.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID3zCCAsmgAwIBAgIUV/4FfTrYMHCb7AxAeI/zAHowUjQwCwYJKoZIhvcNAQEL +MIGGMYGDMDYGA1UEAxMvZjFmZTFkYmEtZmIwNS00OTk2LThmOTUtYTgxNDIyMTk4 +ZGI5LXVzZXJkZXZpY2UwCQYDVQQGEwJDSDAKBgNVBAcTA1p1ZzAKBgNVBAgTA1p1 +ZzARBgNVBAoTClZlcmVpZ24gQUcwEwYDVQQLEwxCdXNpbmVzcyBEZXAwHhcNMTgx +MTIzMjMwMDAwWhcNMjMxMTI0MjI1OTU5WjCBjjGBizA+BgNVBAMTNzNub2JKdk9n +ZkstZjFmZTFkYmEtZmIwNS00OTk2LThmOTUtYTgxNDIyMTk4ZGI5LW9uZXRpbWUw +CQYDVQQGEwJDSDAKBgNVBAcTA1p1ZzAKBgNVBAgTA1p1ZzARBgNVBAoTClZlcmVp +Z24gQUcwEwYDVQQLEwxCdXNpbmVzcyBEZXAwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQC/x1dyX2PgJs6oe/BcBT5XTJXtipme29mtI/FmTq5vuAFopqvA +Eg084hNM7Q++wKyKG8F0ABF2pUOYvo6Pq6rXQqMbxUaO47NNefh4v/f6hBoMfSYm +XtEPji8NjIcIrQ3U8yTWquaHi13hcoG/cVf16Doem5AxUdQ6a9e/jA1VU2MmSZxt +hkABKPWdw1pfFJDLWhFrCjhePKrBxw2ArmNx66fdzpi5XK0fC2TbHD2NVc8cbYOz +F4h3knGZo2sq4/MBevyVhzXZNn2Pjf3PbOxp1O8CLx86MuDTLDkSVjmP0yXBf6rL +FMWtEfv1Jmf3I61meIuOcw6wvXRKzPnAKpLRAgMBAAGjPzA9MAwGA1UdEwEB/wQC +MAAwDgYDVR0PAQH/BAQDAgDwMB0GA1UdDgQWBBSjYCO+w2VQgpdkCvoHgdgZzOPz +BTALBgkqhkiG9w0BAQsDggEBAD6MAP/l4jkJjIJinJ1uPuA6wOVBff1Beb6JV2jo +ay1099VkFVHe/tEeyDcx2j+a2CSKsLdOoG/RjcBup3XsumxlqQS6r9GjZLwtNkxI +nFNLqwv2PbXesSLTR1mjm/6BdcjGEgcxllkST2uYb8DMwbASNQT+rvmGy85b3D4m +tk3scetFWozkfs+lRUql1dWf2aZTcN+IIZlSpxEZu+w1w8yMoDvVV0pCz49d7WIA +RIXDE8QJpiAqszuCbqsNdAbr/pAyYzIAIfKKuteWaViesdY26pZfCe2JsfUTDWSi +PDrFAlkbtB5LGYd4Tfliuvud6I/87GZsuWcCYoS4wXDkRS4= +-----END CERTIFICATE----- diff --git a/desktop/qa/deployment_misc/test_dp_version.cxx b/desktop/qa/deployment_misc/test_dp_version.cxx new file mode 100644 index 0000000000..1b8fb90867 --- /dev/null +++ b/desktop/qa/deployment_misc/test_dp_version.cxx @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <cstddef> +#include <sal/types.h> + +#include <cppunit/TestAssert.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/plugin/TestPlugIn.h> +#include <rtl/ustring.hxx> + +#include "../../source/deployment/inc/dp_version.hxx" + +namespace { + +class Test: public ::CppUnit::TestFixture { +public: + void test(); + + CPPUNIT_TEST_SUITE(Test); + CPPUNIT_TEST(test); + CPPUNIT_TEST_SUITE_END(); +}; + +void Test::test() { + struct Data { + OUString version1; + OUString version2; + ::dp_misc::Order order; + }; + static Data const data[] = { + { OUString(), + OUString("0.0000.00.0"), + ::dp_misc::EQUAL }, + { OUString(".01"), + OUString("0.1"), + ::dp_misc::EQUAL }, + { OUString("10"), + OUString("2"), + ::dp_misc::GREATER }, + { OUString("9223372036854775808"), + // 2^63 + OUString("9223372036854775807"), + ::dp_misc::GREATER } + }; + for (std::size_t i = 0; i < std::size(data); ++i) { + CPPUNIT_ASSERT_EQUAL( + data[i].order, + ::dp_misc::compareVersions(data[i].version1, data[i].version2)); + static ::dp_misc::Order const reverse[3] = { + ::dp_misc::GREATER, ::dp_misc::EQUAL, ::dp_misc::LESS + }; + CPPUNIT_ASSERT_EQUAL( + reverse[data[i].order], + ::dp_misc::compareVersions(data[i].version2, data[i].version1)); + } +} + +CPPUNIT_TEST_SUITE_REGISTRATION(Test); + +} + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/qa/desktop_app/test_desktop_app.cxx b/desktop/qa/desktop_app/test_desktop_app.cxx new file mode 100644 index 0000000000..0e86368e63 --- /dev/null +++ b/desktop/qa/desktop_app/test_desktop_app.cxx @@ -0,0 +1,142 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/types.h> + +#include <cppunit/TestAssert.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/plugin/TestPlugIn.h> +#include <rtl/ustring.hxx> +#include <cppuhelper/bootstrap.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/lang/XMultiComponentFactory.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <comphelper/processfactory.hxx> + +#include "../../source/app/cmdlineargs.hxx" + +namespace { + +class Test: public ::CppUnit::TestFixture { +public: + void testTdf100837(); + + CPPUNIT_TEST_SUITE(Test); + CPPUNIT_TEST(testTdf100837); + CPPUNIT_TEST_SUITE_END(); +}; + +class TestSupplier : public desktop::CommandLineArgs::Supplier { +public: + explicit TestSupplier(const std::initializer_list<OUString>& args) : m_args(args) {} + + virtual std::optional< OUString > getCwdUrl() override { return std::optional< OUString >(); } + virtual bool next(OUString * argument) override { + CPPUNIT_ASSERT(argument != nullptr); + if (m_index < m_args.size()) { + *argument = m_args[m_index++]; + return true; + } + else { + return false; + } + } +private: + std::vector< OUString > m_args; + std::vector< OUString >::size_type m_index = 0; +}; + +// Test Office URI Schemes support +void Test::testTdf100837() { + auto xContext = ::cppu::defaultBootstrap_InitialComponentContext(); + ::css::uno::Reference<::css::lang::XMultiComponentFactory> xFactory(xContext->getServiceManager()); + ::css::uno::Reference<::css::lang::XMultiServiceFactory> xSM(xFactory, ::css::uno::UNO_QUERY_THROW); + // Without this we're crashing because callees are using getProcessServiceFactory + ::comphelper::setProcessServiceFactory(xSM); + + { + // 1. Test default behaviour: Office URIs define open mode + TestSupplier supplier{ "foo", "ms-word:ofe|u|bar1", "ms-word:ofv|u|bar2", "ms-word:nft|u|bar3", "baz" }; + desktop::CommandLineArgs args(supplier); + auto vOpenList = args.GetOpenList(); + auto vForceOpenList = args.GetForceOpenList(); + auto vViewList = args.GetViewList(); + auto vForceNewList = args.GetForceNewList(); + // 2 documents go to Open list: foo; baz + CPPUNIT_ASSERT_EQUAL(decltype(vOpenList.size())(2), vOpenList.size()); + CPPUNIT_ASSERT_EQUAL(OUString("foo"), vOpenList[0]); + CPPUNIT_ASSERT_EQUAL(OUString("baz"), vOpenList[1]); + // 1 document goes to ForceOpen list: bar1 + CPPUNIT_ASSERT_EQUAL(decltype(vForceOpenList.size())(1), vForceOpenList.size()); + CPPUNIT_ASSERT_EQUAL(OUString("bar1"), vForceOpenList[0]); + // 1 document goes to View list: bar2 + CPPUNIT_ASSERT_EQUAL(decltype(vViewList.size())(1), vViewList.size()); + CPPUNIT_ASSERT_EQUAL(OUString("bar2"), vViewList[0]); + // 1 document goes to ForceNew list: bar3 + CPPUNIT_ASSERT_EQUAL(decltype(vForceNewList.size())(1), vForceNewList.size()); + CPPUNIT_ASSERT_EQUAL(OUString("bar3"), vForceNewList[0]); + } + + { + // 2. Test explicit open mode arguments. Office URI commands should have no effect + TestSupplier supplier{ "--view", "ms-word:ofe|u|foo", "-o", "ms-word:ofv|u|bar", "ms-word:nft|u|baz" }; + desktop::CommandLineArgs args(supplier); + auto vViewList = args.GetViewList(); + auto vForceOpenList = args.GetForceOpenList(); + // 1 document goes to View list: foo + CPPUNIT_ASSERT_EQUAL(decltype(vViewList.size())(1), vViewList.size()); + CPPUNIT_ASSERT_EQUAL(OUString("foo"), vViewList[0]); + // 2 documents go to ForceOpen list: bar, baz + CPPUNIT_ASSERT_EQUAL(decltype(vForceOpenList.size())(2), vForceOpenList.size()); + CPPUNIT_ASSERT_EQUAL(OUString("bar"), vForceOpenList[0]); + CPPUNIT_ASSERT_EQUAL(OUString("baz"), vForceOpenList[1]); + } + + { + // 3. Test encoded URLs + TestSupplier supplier{ "foo", "ms-word:ofe%7Cu%7cbar1", "ms-word:ofv%7cu%7Cbar2", "ms-word:nft%7Cu%7cbar3", "baz" }; + desktop::CommandLineArgs args(supplier); + auto vOpenList = args.GetOpenList(); + auto vForceOpenList = args.GetForceOpenList(); + auto vViewList = args.GetViewList(); + auto vForceNewList = args.GetForceNewList(); + // 2 documents go to Open list: foo; baz + CPPUNIT_ASSERT_EQUAL(decltype(vOpenList.size())(2), vOpenList.size()); + CPPUNIT_ASSERT_EQUAL(OUString("foo"), vOpenList[0]); + CPPUNIT_ASSERT_EQUAL(OUString("baz"), vOpenList[1]); + // 1 document goes to ForceOpen list: bar1 + CPPUNIT_ASSERT_EQUAL(decltype(vForceOpenList.size())(1), vForceOpenList.size()); + CPPUNIT_ASSERT_EQUAL(OUString("bar1"), vForceOpenList[0]); + // 1 document goes to View list: bar2 + CPPUNIT_ASSERT_EQUAL(decltype(vViewList.size())(1), vViewList.size()); + CPPUNIT_ASSERT_EQUAL(OUString("bar2"), vViewList[0]); + // 1 document goes to ForceNew list: bar3 + CPPUNIT_ASSERT_EQUAL(decltype(vForceNewList.size())(1), vForceNewList.size()); + CPPUNIT_ASSERT_EQUAL(OUString("bar3"), vForceNewList[0]); + } +} + +CPPUNIT_TEST_SUITE_REGISTRATION(Test); + +} + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/qa/desktop_lib/test_desktop_lib.cxx b/desktop/qa/desktop_lib/test_desktop_lib.cxx new file mode 100644 index 0000000000..b3410bd8eb --- /dev/null +++ b/desktop/qa/desktop_lib/test_desktop_lib.cxx @@ -0,0 +1,3713 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <config_oox.h> +#include <memory> +#include <string_view> + +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/text/XTextDocument.hpp> +#include <com/sun/star/awt/Key.hpp> +#include <com/sun/star/awt/XReschedule.hpp> +#include <com/sun/star/awt/Toolkit.hpp> +#include <com/sun/star/drawing/XDrawPageSupplier.hpp> +#include <com/sun/star/text/TextContentAnchorType.hpp> +#include <boost/property_tree/json_parser.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/util/XCloseable.hpp> + +#include <vcl/scheduler.hxx> +#include <vcl/svapp.hxx> +#include <vcl/syswin.hxx> +#include <vcl/window.hxx> +#include <vcl/ctrl.hxx> +#include <vcl/uitest/uiobject.hxx> +#include <comphelper/processfactory.hxx> +#include <rtl/math.hxx> +#include <rtl/uri.hxx> +#include <sfx2/app.hxx> +#include <sfx2/childwin.hxx> +#include <sfx2/lokhelper.hxx> +#include <test/unoapi_test.hxx> +#include <comphelper/lok.hxx> +#include <comphelper/propertysequence.hxx> +#include <osl/conditn.hxx> +#include <svl/srchitem.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <unotools/tempfile.hxx> +#include <sfx2/viewsh.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/sidebar/SidebarController.hxx> +#include <sfx2/sidebar/SidebarDockingWindow.hxx> +#include <unotools/datetime.hxx> +#include <unotools/syslocaleoptions.hxx> +#include <comphelper/string.hxx> +#include <comphelper/scopeguard.hxx> +#include <cairo.h> +#include <config_features.h> +#include <config_fonts.h> +#include <config_mpl.h> +#include <tools/json_writer.hxx> +#include <o3tl/unit_conversion.hxx> +#include <o3tl/string_view.hxx> + +#include <lib/init.hxx> +#include <svx/svxids.hrc> + +#include <cppunit/TestAssert.h> +#include <vcl/BitmapTools.hxx> +#include <vcl/filter/PngImageWriter.hxx> +#include <vcl/filter/PDFiumLibrary.hxx> +#include <svtools/colorcfg.hxx> +#include <sal/types.h> + +#if USE_TLS_NSS +#include <nss.h> +#endif + +using namespace com::sun::star; +using namespace desktop; + +static LibreOfficeKitDocumentType getDocumentTypeFromName(const char* pName) +{ + CPPUNIT_ASSERT_MESSAGE("Document name must be valid.", pName != nullptr); + + const std::string name(pName); + CPPUNIT_ASSERT_MESSAGE("Document name must include extension.", name.size() > 4); + + const auto it = name.rfind('.'); + if (it != std::string::npos) + { + const std::string ext = name.substr(it); + + if (ext == ".ods") + return LOK_DOCTYPE_SPREADSHEET; + + if (ext == ".odp") + return LOK_DOCTYPE_PRESENTATION; + } + + CPPUNIT_ASSERT_MESSAGE("Document name must include extension.", it != std::string::npos); + return LOK_DOCTYPE_TEXT; +} + +class DesktopLOKTest : public UnoApiTest +{ +public: + DesktopLOKTest() : UnoApiTest("/desktop/qa/data/"), + m_nSelectionBeforeSearchResult(0), + m_nSelectionAfterSearchResult(0), + m_bModified(false), + m_nTrackChanges(0) + { + } + ~DesktopLOKTest(); + + void readFileIntoByteVector( + std::u16string_view sFilename, std::vector<sal_uInt8> & rByteVector); + + virtual void setUp() override + { + comphelper::LibreOfficeKit::setActive(true); + + UnoApiTest::setUp(); + } + + virtual void tearDown() override + { + closeDoc(); + + // documents are already closed, no need to call UnoApiTest::tearDown + test::BootstrapFixture::tearDown(); + + comphelper::LibreOfficeKit::setActive(false); + } + + std::unique_ptr<LibLODocument_Impl> + loadDocImpl(const char* pName, LibreOfficeKitDocumentType eType); + +private: + std::unique_ptr<LibLODocument_Impl> + loadDocImpl(const char* pName); + +public: + std::unique_ptr<LibLODocument_Impl> + loadDocUrlImpl(const OUString& rFileURL, LibreOfficeKitDocumentType eType); + + LibLODocument_Impl* loadDocUrl(const OUString& rFileURL, LibreOfficeKitDocumentType eType); + LibLODocument_Impl* loadDoc(const char* pName, LibreOfficeKitDocumentType eType); + LibLODocument_Impl* loadDoc(const char* pName) + { + return loadDoc(pName, getDocumentTypeFromName(pName)); + } + + void closeDoc(std::unique_ptr<LibLODocument_Impl>& loDocument); + void closeDoc() { closeDoc(m_pDocument); } + static void callback(int nType, const char* pPayload, void* pData); + void callbackImpl(int nType, const char* pPayload); + + void testGetStyles(); + void testGetFonts(); + void testCreateView(); + void testGetFilterTypes(); + void testGetPartPageRectangles(); + void testSearchCalc(); + void testSearchAllNotificationsCalc(); + void testPaintTile(); + void testSaveAs(); + void testSaveAsJsonOptions(); + void testSaveAsCalc(); + void testPasteWriter(); + void testPasteWriterJPEG(); + void testUndoWriter(); + void testRowColumnHeaders(); + void testHiddenRowHeaders(); + void testCellCursor(); + void testCommandResult(); + void testWriterComments(); + void testSheetOperations(); + void testSheetSelections(); + void testSheetDragDrop(); + void testContextMenuCalc(); + void testContextMenuWriter(); + void testContextMenuImpress(); + void testNotificationCompression(); + void testTileInvalidationCompression(); + void testPartInInvalidation(); + void testBinaryCallback(); + void testInput(); + void testRedlineWriter(); + void testTrackChanges(); + void testRedlineCalc(); + void testPaintPartTile(); + void testPaintPartTileDifferentSchemes(); +#if HAVE_MORE_FONTS + void testGetFontSubset(); +#endif + void testCommentsWriter(); + void testCommentsCalc(); + void testCommentsImpress(); + void testCommentsCallbacksWriter(); + void testCommentsAddEditDeleteDraw(); + void testRunMacro(); + void testExtractParameter(); + void testGetSignatureState_NonSigned(); + void testGetSignatureState_Signed(); +#if 0 // broken with system nss on RHEL 7 + void testInsertCertificate_DER_ODT(); + void testInsertCertificate_PEM_ODT(); + void testInsertCertificate_PEM_DOCX(); +#endif + void testSignDocument_PEM_PDF(); + void testTextSelectionHandles(); + void testComplexSelection(); + void testSpellcheckerMultiView(); + void testDialogPaste(); + void testCalcSaveAs(); + void testControlState(); + void testMetricField(); + void testMultiDocuments(); + void testJumpCursor(); + void testRenderSearchResult_WriterNode(); + void testRenderSearchResult_CommonNode(); + void testNoDuplicateTableSelection(); + void testMultiViewTableSelection(); + void testColorPaletteCallback(); + void testABI(); + + CPPUNIT_TEST_SUITE(DesktopLOKTest); + CPPUNIT_TEST(testGetStyles); + CPPUNIT_TEST(testGetFonts); + CPPUNIT_TEST(testCreateView); + CPPUNIT_TEST(testGetFilterTypes); + CPPUNIT_TEST(testGetPartPageRectangles); + CPPUNIT_TEST(testSearchCalc); + CPPUNIT_TEST(testSearchAllNotificationsCalc); + CPPUNIT_TEST(testPaintTile); + CPPUNIT_TEST(testSaveAs); + CPPUNIT_TEST(testSaveAsJsonOptions); + CPPUNIT_TEST(testSaveAsCalc); + CPPUNIT_TEST(testPasteWriter); + CPPUNIT_TEST(testPasteWriterJPEG); + CPPUNIT_TEST(testUndoWriter); + CPPUNIT_TEST(testRowColumnHeaders); + CPPUNIT_TEST(testHiddenRowHeaders); + CPPUNIT_TEST(testCellCursor); + CPPUNIT_TEST(testCommandResult); + CPPUNIT_TEST(testWriterComments); + CPPUNIT_TEST(testSheetOperations); + CPPUNIT_TEST(testSheetSelections); + CPPUNIT_TEST(testSheetDragDrop); + CPPUNIT_TEST(testContextMenuCalc); + CPPUNIT_TEST(testContextMenuWriter); + CPPUNIT_TEST(testContextMenuImpress); + CPPUNIT_TEST(testNotificationCompression); + CPPUNIT_TEST(testTileInvalidationCompression); + CPPUNIT_TEST(testPartInInvalidation); + CPPUNIT_TEST(testBinaryCallback); + CPPUNIT_TEST(testInput); + CPPUNIT_TEST(testRedlineWriter); + CPPUNIT_TEST(testTrackChanges); + CPPUNIT_TEST(testRedlineCalc); + CPPUNIT_TEST(testPaintPartTile); + CPPUNIT_TEST(testPaintPartTileDifferentSchemes); +#if HAVE_MORE_FONTS + CPPUNIT_TEST(testGetFontSubset); +#endif + CPPUNIT_TEST(testCommentsWriter); + CPPUNIT_TEST(testCommentsCalc); + CPPUNIT_TEST(testCommentsImpress); + CPPUNIT_TEST(testCommentsCallbacksWriter); + CPPUNIT_TEST(testCommentsAddEditDeleteDraw); + CPPUNIT_TEST(testRunMacro); + CPPUNIT_TEST(testExtractParameter); + CPPUNIT_TEST(testGetSignatureState_Signed); + CPPUNIT_TEST(testGetSignatureState_NonSigned); +#if !MPL_HAVE_SUBSET +#if 0 // broken with system nss on RHEL 7 + CPPUNIT_TEST(testInsertCertificate_DER_ODT); + CPPUNIT_TEST(testInsertCertificate_PEM_ODT); + CPPUNIT_TEST(testInsertCertificate_PEM_DOCX); +#endif + CPPUNIT_TEST(testSignDocument_PEM_PDF); +#endif + CPPUNIT_TEST(testTextSelectionHandles); + CPPUNIT_TEST(testComplexSelection); + CPPUNIT_TEST(testSpellcheckerMultiView); + CPPUNIT_TEST(testDialogPaste); + CPPUNIT_TEST(testCalcSaveAs); + CPPUNIT_TEST(testControlState); + CPPUNIT_TEST(testMetricField); + CPPUNIT_TEST(testMultiDocuments); + CPPUNIT_TEST(testJumpCursor); + CPPUNIT_TEST(testRenderSearchResult_WriterNode); + CPPUNIT_TEST(testRenderSearchResult_CommonNode); + CPPUNIT_TEST(testNoDuplicateTableSelection); + CPPUNIT_TEST(testMultiViewTableSelection); + CPPUNIT_TEST(testColorPaletteCallback); + CPPUNIT_TEST(testABI); + CPPUNIT_TEST_SUITE_END(); + + OString m_aTextSelection; + OString m_aTextSelectionStart; + OString m_aTextSelectionEnd; + std::vector<OString> m_aSearchResultSelection; + std::vector<int> m_aSearchResultPart; + int m_nSelectionBeforeSearchResult; + int m_nSelectionAfterSearchResult; + + // for testCommandResult + osl::Condition m_aCommandResultCondition; + OString m_aCommandResult; + + // for testModifiedStatus + osl::Condition m_aStateChangedCondition; + bool m_bModified; + int m_nTrackChanges; + + // for testContextMenu{Calc, Writer} + osl::Condition m_aContextMenuCondition; + boost::property_tree::ptree m_aContextMenuResult; + + std::unique_ptr<LibLODocument_Impl> m_pDocument; +}; + +DesktopLOKTest::~DesktopLOKTest() +{ +#if USE_TLS_NSS + NSS_Shutdown(); +#endif +} + +static Control* GetFocusControl(vcl::Window const * pParent) +{ + sal_uInt16 nChildren = pParent->GetChildCount(); + for (sal_uInt16 nChild = 0; nChild < nChildren; ++nChild) + { + vcl::Window* pChild = pParent->GetChild( nChild ); + Control* pCtrl = dynamic_cast<Control*>(pChild); + if (pCtrl && pCtrl->HasControlFocus()) + return pCtrl; + + Control* pSubCtrl = GetFocusControl( pChild ); + if (pSubCtrl) + return pSubCtrl; + } + return nullptr; +} + +std::unique_ptr<LibLODocument_Impl> +DesktopLOKTest::loadDocUrlImpl(const OUString& rFileURL, LibreOfficeKitDocumentType eType) +{ + OUString aService; + switch (eType) + { + case LOK_DOCTYPE_TEXT: + aService = "com.sun.star.text.TextDocument"; + break; + case LOK_DOCTYPE_SPREADSHEET: + aService = "com.sun.star.sheet.SpreadsheetDocument"; + break; + case LOK_DOCTYPE_PRESENTATION: + aService = "com.sun.star.presentation.PresentationDocument"; + break; + default: + CPPUNIT_ASSERT(false); + break; + } + + static int nDocumentIdCounter = 0; + SfxViewShell::SetCurrentDocId(ViewShellDocId(nDocumentIdCounter)); + mxComponent = loadFromDesktop(rFileURL, aService); + + std::unique_ptr<LibLODocument_Impl> pDocument(new LibLODocument_Impl(mxComponent, nDocumentIdCounter)); + ++nDocumentIdCounter; + + return pDocument; +} + +std::unique_ptr<LibLODocument_Impl> +DesktopLOKTest::loadDocImpl(const char* pName, LibreOfficeKitDocumentType eType) +{ + OUString aFileURL = createFileURL(OUString::createFromAscii(pName)); + return loadDocUrlImpl(aFileURL, eType); +} + +std::unique_ptr<LibLODocument_Impl> +DesktopLOKTest::loadDocImpl(const char* pName) +{ + return loadDocImpl(pName, getDocumentTypeFromName(pName)); +} + +LibLODocument_Impl* DesktopLOKTest::loadDocUrl(const OUString& rFileURL, LibreOfficeKitDocumentType eType) +{ + m_pDocument = loadDocUrlImpl(rFileURL, eType); + return m_pDocument.get(); +} + +LibLODocument_Impl* DesktopLOKTest::loadDoc(const char* pName, LibreOfficeKitDocumentType eType) +{ + m_pDocument = loadDocImpl(pName, eType); + return m_pDocument.get(); +} + +void DesktopLOKTest::closeDoc(std::unique_ptr<LibLODocument_Impl>& pDocument) +{ + if (pDocument) + { + pDocument->pClass->registerCallback(pDocument.get(), nullptr, nullptr); + pDocument.reset(); + } + + if (mxComponent.is()) + { + css::uno::Reference<util::XCloseable> xCloseable(mxComponent, css::uno::UNO_QUERY_THROW); + xCloseable->close(false); + mxComponent.clear(); + } +} + +void DesktopLOKTest::callback(int nType, const char* pPayload, void* pData) +{ + static_cast<DesktopLOKTest*>(pData)->callbackImpl(nType, pPayload); +} + +void DesktopLOKTest::callbackImpl(int nType, const char* pPayload) +{ + switch (nType) + { + case LOK_CALLBACK_TEXT_SELECTION: + { + m_aTextSelection = pPayload; + if (m_aSearchResultSelection.empty()) + ++m_nSelectionBeforeSearchResult; + else + ++m_nSelectionAfterSearchResult; + } + break; + case LOK_CALLBACK_TEXT_SELECTION_START: + m_aTextSelectionStart = pPayload; + break; + case LOK_CALLBACK_TEXT_SELECTION_END: + m_aTextSelectionEnd = pPayload; + break; + case LOK_CALLBACK_SEARCH_RESULT_SELECTION: + { + m_aSearchResultSelection.clear(); + boost::property_tree::ptree aTree; + std::stringstream aStream(pPayload); + boost::property_tree::read_json(aStream, aTree); + for (const boost::property_tree::ptree::value_type& rValue : aTree.get_child("searchResultSelection")) + { + m_aSearchResultSelection.emplace_back(rValue.second.get<std::string>("rectangles").c_str()); + m_aSearchResultPart.push_back(std::atoi(rValue.second.get<std::string>("part").c_str())); + } + } + break; + case LOK_CALLBACK_UNO_COMMAND_RESULT: + { + m_aCommandResult = pPayload; + m_aCommandResultCondition.set(); + } + break; + case LOK_CALLBACK_STATE_CHANGED: + { + OString aPayload(pPayload); + OString aPrefix(".uno:ModifiedStatus="_ostr); + if (aPayload.startsWith(aPrefix)) + { + m_bModified = aPayload.copy(aPrefix.getLength()).toBoolean(); + m_aStateChangedCondition.set(); + } + else if (aPayload.startsWith(".uno:TrackChanges=") && aPayload.endsWith("=true")) + ++m_nTrackChanges; + } + break; + case LOK_CALLBACK_CONTEXT_MENU: + { + m_aContextMenuResult.clear(); + std::stringstream aStream(pPayload); + boost::property_tree::read_json(aStream, m_aContextMenuResult); + m_aContextMenuCondition.set(); + } + break; + } +} + +void DesktopLOKTest::testGetStyles() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + boost::property_tree::ptree aTree; + char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:StyleApply"); + std::stringstream aStream(pJSON); + boost::property_tree::read_json(aStream, aTree); + CPPUNIT_ASSERT( !aTree.empty() ); + CPPUNIT_ASSERT_EQUAL( std::string(".uno:StyleApply"), aTree.get_child("commandName").get_value<std::string>() ); + + boost::property_tree::ptree aValues = aTree.get_child("commandValues"); + CPPUNIT_ASSERT( !aValues.empty() ); + for (const auto& rPair : aValues) + { + if( rPair.first != "ClearStyle") + { + CPPUNIT_ASSERT( !rPair.second.empty()); + } + if (rPair.first != "CharacterStyles" && + rPair.first != "ParagraphStyles" && + rPair.first != "FrameStyles" && + rPair.first != "PageStyles" && + rPair.first != "NumberingStyles" && + rPair.first != "CellStyles" && + rPair.first != "ShapeStyles" && + rPair.first != "TableStyles" && + rPair.first != "HeaderFooter" && + rPair.first != "Commands") + { + CPPUNIT_FAIL("Unknown style family: " + rPair.first); + } + } + free(pJSON); +} + +void DesktopLOKTest::testGetFonts() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_presentation.odp"); + boost::property_tree::ptree aTree; + char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:CharFontName"); + std::stringstream aStream(pJSON); + boost::property_tree::read_json(aStream, aTree); + CPPUNIT_ASSERT( !aTree.empty() ); + CPPUNIT_ASSERT_EQUAL( std::string(".uno:CharFontName"), aTree.get_child("commandName").get_value<std::string>() ); + + boost::property_tree::ptree aValues = aTree.get_child("commandValues"); + CPPUNIT_ASSERT( !aValues.empty() ); + for (const auto& rPair : aValues) + { + // check that we have font sizes available for each font + CPPUNIT_ASSERT( !rPair.second.empty()); + } + free(pJSON); +} + +void DesktopLOKTest::testCreateView() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + CPPUNIT_ASSERT_EQUAL(1, pDocument->m_pDocumentClass->getViewsCount(pDocument)); + + int nId0 = pDocument->m_pDocumentClass->getView(pDocument); + int nId1 = pDocument->m_pDocumentClass->createView(pDocument); + CPPUNIT_ASSERT_EQUAL(2, pDocument->m_pDocumentClass->getViewsCount(pDocument)); + + // Test getViewIds(). + std::vector<int> aViewIds(2); + CPPUNIT_ASSERT(pDocument->m_pDocumentClass->getViewIds(pDocument, aViewIds.data(), aViewIds.size())); + CPPUNIT_ASSERT_EQUAL(nId0, aViewIds[0]); + CPPUNIT_ASSERT_EQUAL(nId1, aViewIds[1]); + + // Make sure the created view is the active one, then switch to the old + // one. + CPPUNIT_ASSERT_EQUAL(nId1, pDocument->m_pDocumentClass->getView(pDocument)); + pDocument->m_pDocumentClass->setView(pDocument, nId0); + CPPUNIT_ASSERT_EQUAL(nId0, pDocument->m_pDocumentClass->getView(pDocument)); + + pDocument->m_pDocumentClass->destroyView(pDocument, nId1); + CPPUNIT_ASSERT_EQUAL(1, pDocument->m_pDocumentClass->getViewsCount(pDocument)); +} + +void DesktopLOKTest::testGetPartPageRectangles() +{ + // Test that we get as many page rectangles as expected: blank document is + // one page. + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + char* pRectangles = pDocument->pClass->getPartPageRectangles(pDocument); + OUString sRectangles = OUString::fromUtf8(pRectangles); + + std::vector<OUString> aRectangles; + sal_Int32 nIndex = 0; + do + { + OUString aRectangle = sRectangles.getToken(0, ';', nIndex); + if (!aRectangle.isEmpty()) + aRectangles.push_back(aRectangle); + } + while (nIndex >= 0); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aRectangles.size()); + + free(pRectangles); +} + +void DesktopLOKTest::testGetFilterTypes() +{ + LibLibreOffice_Impl aOffice; + char* pJSON = aOffice.m_pOfficeClass->getFilterTypes(&aOffice); + + std::stringstream aStream(pJSON); + boost::property_tree::ptree aTree; + boost::property_tree::read_json(aStream, aTree); + + CPPUNIT_ASSERT(!aTree.empty()); + CPPUNIT_ASSERT_EQUAL(std::string("application/vnd.oasis.opendocument.text"), aTree.get_child("writer8").get_child("MediaType").get_value<std::string>()); + free(pJSON); +} + +void DesktopLOKTest::testSearchCalc() +{ + LibLibreOffice_Impl aOffice; + LibLODocument_Impl* pDocument = loadDoc("search.ods"); + pDocument->pClass->initializeForRendering(pDocument, nullptr); + pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); + + uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence( + { + {"SearchItem.SearchString", uno::Any(OUString("foo"))}, + {"SearchItem.Backward", uno::Any(false)}, + {"SearchItem.Command", uno::Any(static_cast<sal_uInt16>(SvxSearchCmd::FIND_ALL))}, + })); + dispatchCommand(mxComponent, ".uno:ExecuteSearch", aPropertyValues); + + std::vector<OString> aSelections; + sal_Int32 nIndex = 0; + do + { + OString aToken = m_aTextSelection.getToken(0, ';', nIndex); + aSelections.push_back(aToken); + } while (nIndex >= 0); + // This was 1, find-all only found one match. + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), aSelections.size()); + // Make sure that we get exactly as many rectangle lists as matches. + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), m_aSearchResultSelection.size()); + // Result is on the first sheet. + CPPUNIT_ASSERT_EQUAL(0, m_aSearchResultPart[0]); +} + +void DesktopLOKTest::testSearchAllNotificationsCalc() +{ + LibLibreOffice_Impl aOffice; + LibLODocument_Impl* pDocument = loadDoc("search.ods"); + pDocument->pClass->initializeForRendering(pDocument, nullptr); + pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); + + uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence( + { + {"SearchItem.SearchString", uno::Any(OUString("foo"))}, + {"SearchItem.Backward", uno::Any(false)}, + {"SearchItem.Command", uno::Any(static_cast<sal_uInt16>(SvxSearchCmd::FIND_ALL))}, + })); + dispatchCommand(mxComponent, ".uno:ExecuteSearch", aPropertyValues); + + // This was 1, make sure that we get no notifications about selection changes during search. + CPPUNIT_ASSERT_EQUAL(0, m_nSelectionBeforeSearchResult); + // But we do get the selection afterwards. + CPPUNIT_ASSERT(m_nSelectionAfterSearchResult > 0); +} + +void DesktopLOKTest::testPaintTile() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + int nCanvasWidth = 100; + int nCanvasHeight = 300; + sal_Int32 nStride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, nCanvasWidth); + std::vector<unsigned char> aBuffer(nStride * nCanvasHeight); + int nTilePosX = 0; + int nTilePosY = 0; + int nTileWidth = 1000; + int nTileHeight = 3000; + + // This used to crash: paintTile() implementation did not handle + // nCanvasWidth != nCanvasHeight correctly, as usually both are just always + // 256. + pDocument->pClass->paintTile(pDocument, aBuffer.data(), nCanvasWidth, nCanvasHeight, nTilePosX, nTilePosY, nTileWidth, nTileHeight); + + // This crashed in OutputDevice::DrawDeviceAlphaBitmap(). + nCanvasWidth = 200; + nCanvasHeight = 200; + nTileWidth = 4000; + nTileHeight = 4000; + aBuffer.resize(nCanvasWidth * nCanvasHeight * 4); + pDocument->pClass->paintTile(pDocument, aBuffer.data(), nCanvasWidth, nCanvasHeight, nTilePosX, nTilePosY, nTileWidth, nTileHeight); +} + +void DesktopLOKTest::testSaveAs() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, maTempFile.GetURL().toUtf8().getStr(), "png", nullptr)); +} + +void DesktopLOKTest::testSaveAsJsonOptions() +{ + // Given a document with 3 pages: + LibLODocument_Impl* pDocument = loadDoc("3page.odg"); + + // When exporting that document to PDF, skipping the first page: + OString aOptions("{\"PageRange\":{\"type\":\"string\",\"value\":\"2-\"}}"_ostr); + CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, maTempFile.GetURL().toUtf8().getStr(), "pdf", aOptions.getStr())); + + std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get(); + if (!pPDFium) + return; + + // Then make sure the resulting PDF has 2 pages: + std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument + = parsePDFExport(); + // Without the accompanying fix in place, this test would have failed with: + // - Expected: 2 + // - Actual : 3 + // i.e. FilterOptions was ignored. + CPPUNIT_ASSERT_EQUAL(2, pPdfDocument->getPageCount()); +} + +void DesktopLOKTest::testSaveAsCalc() +{ + LibLODocument_Impl* pDocument = loadDoc("search.ods"); + CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, maTempFile.GetURL().toUtf8().getStr(), "png", nullptr)); +} + +void DesktopLOKTest::testPasteWriter() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + OString aText("hello"_ostr); + + CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "text/plain;charset=utf-8", aText.getStr(), aText.getLength())); + + pDocument->pClass->postUnoCommand(pDocument, ".uno:SelectAll", nullptr, false); + Scheduler::ProcessEventsToIdle(); + char* pText = pDocument->pClass->getTextSelection(pDocument, "text/plain;charset=utf-8", nullptr); + CPPUNIT_ASSERT_EQUAL("hello"_ostr, OString(pText)); + free(pText); + + // textt/plain should be rejected. + CPPUNIT_ASSERT(!pDocument->pClass->paste(pDocument, "textt/plain;charset=utf-8", aText.getStr(), aText.getLength())); + // Writer is expected to support text/html. + CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "text/html", aText.getStr(), aText.getLength())); + + // Overwrite doc contents with a HTML paste. + pDocument->pClass->postUnoCommand(pDocument, ".uno:SelectAll", nullptr, false); + Scheduler::ProcessEventsToIdle(); + OString aComment("foo <!-- bar --> baz"_ostr); + CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "text/html", aComment.getStr(), aComment.getLength())); + + // Check if we have a comment. + uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY); + uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xTextDocument->getText(), uno::UNO_QUERY); + uno::Reference<container::XEnumeration> xParagraphEnumeration = xParagraphEnumerationAccess->createEnumeration(); + uno::Reference<container::XEnumerationAccess> xParagraph(xParagraphEnumeration->nextElement(), uno::UNO_QUERY); + uno::Reference<container::XEnumeration> xTextPortionEnumeration = xParagraph->createEnumeration(); + uno::Reference<beans::XPropertySet> xTextPortion(xTextPortionEnumeration->nextElement(), uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(OUString("Text"), xTextPortion->getPropertyValue("TextPortionType").get<OUString>()); + // Without the accompanying fix in place, this test would have failed, as we had a comment + // between "foo" and "baz". + CPPUNIT_ASSERT(!xTextPortionEnumeration->hasMoreElements()); +} + +void DesktopLOKTest::testPasteWriterJPEG() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + + OUString aFileURL = createFileURL(u"paste.jpg"); + std::ifstream aImageStream(aFileURL.toUtf8().copy(strlen("file://")).getStr()); + std::vector<char> aImageContents((std::istreambuf_iterator<char>(aImageStream)), std::istreambuf_iterator<char>()); + + CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "image/jpeg", aImageContents.data(), aImageContents.size())); + + uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference<drawing::XDrawPage> xDrawPage = xDrawPageSupplier->getDrawPage(); + // This was 0, JPEG was not handled as a format for clipboard paste. + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), xDrawPage->getCount()); + + uno::Reference<beans::XPropertySet> xShape(xDrawPage->getByIndex(0), uno::UNO_QUERY); + // This was text::TextContentAnchorType_AT_PARAGRAPH. + CPPUNIT_ASSERT_EQUAL(text::TextContentAnchorType_AS_CHARACTER, xShape->getPropertyValue("AnchorType").get<text::TextContentAnchorType>()); + + // Delete the pasted picture, and paste again with a custom anchor type. + uno::Reference<lang::XComponent>(xShape, uno::UNO_QUERY_THROW)->dispose(); + uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence( + { + {"AnchorType", uno::Any(static_cast<sal_uInt16>(text::TextContentAnchorType_AT_CHARACTER))}, + })); + dispatchCommand(mxComponent, ".uno:Paste", aPropertyValues); + xShape.set(xDrawPage->getByIndex(0), uno::UNO_QUERY); + // This was text::TextContentAnchorType_AS_CHARACTER, AnchorType argument was ignored. + CPPUNIT_ASSERT_EQUAL(text::TextContentAnchorType_AT_CHARACTER, xShape->getPropertyValue("AnchorType").get<text::TextContentAnchorType>()); +} + +void DesktopLOKTest::testUndoWriter() +{ + // Load a Writer document and press a key. + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 't', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 't', 0); + Scheduler::ProcessEventsToIdle(); + // Get undo info. + boost::property_tree::ptree aTree; + char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:Undo"); + std::stringstream aStream(pJSON); + free(pJSON); + CPPUNIT_ASSERT(!aStream.str().empty()); + boost::property_tree::read_json(aStream, aTree); + // Make sure that pressing a key creates exactly one undo action. + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aTree.get_child("actions").size()); +} + +void DesktopLOKTest::testRowColumnHeaders() +{ + /* + * Payload example: + * + * { + * "rows": [ + * { + * "size": "254.987250637468", + * "text": "1" + * }, + * { + * "size": "509.974501274936", + * "text": "2" + * } + * ], + * "columns": [ + * { + * "size": "1274.93625318734", + * "text": "A" + * }, + * { + * "size": "2549.87250637468", + * "text": "B" + * } + * ] + * } + * + * "size" defines the bottom/right boundary of a row/column in twips (size between 0 and boundary) + * "text" has the header label in UTF-8 + */ + LibLODocument_Impl* pDocument = loadDoc("search.ods"); + + pDocument->pClass->initializeForRendering(pDocument, nullptr); + + tools::Long nWidth = 0; + tools::Long nHeight = 0; + pDocument->m_pDocumentClass->getDocumentSize(pDocument, &nWidth, &nHeight); + tools::Long nX = rtl::math::round(nWidth / 4.0); + tools::Long nY = rtl::math::round(nHeight / 4.0); + nWidth = rtl::math::round(nWidth / 2.0); + nHeight = rtl::math::round(nHeight / 2.0); + + std::stringstream aPayload; + aPayload << ".uno:ViewRowColumnHeaders?x=" << nX << "&y=" << nY << "&width=" << nWidth << "&height=" << nHeight; + + boost::property_tree::ptree aTree; + char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, aPayload.str().c_str()); + std::stringstream aStream(pJSON); + free(pJSON); + + CPPUNIT_ASSERT(!aStream.str().empty()); + + boost::property_tree::read_json(aStream, aTree); + sal_Int32 nPrevious = 0; + bool bFirstHeader = true; + bool bNotEnoughHeaders = true; + for (const boost::property_tree::ptree::value_type& rValue : aTree.get_child("rows")) + { + sal_Int32 nSize = o3tl::toInt32(rValue.second.get<std::string>("size")); + nSize = o3tl::convert(nSize, o3tl::Length::px, o3tl::Length::twip); + OString aText(rValue.second.get<std::string>("text")); + + if (bFirstHeader) + { + CPPUNIT_ASSERT(nSize <= nY); + CPPUNIT_ASSERT_EQUAL("10"_ostr, aText); + bFirstHeader = false; + } + else + { + CPPUNIT_ASSERT(nSize > 0); + CPPUNIT_ASSERT(nPrevious < nSize); + if (nSize > nY + nHeight) + { + bNotEnoughHeaders = false; + break; + } + } + nPrevious = nSize; + } + CPPUNIT_ASSERT(!bNotEnoughHeaders); + + nPrevious = 0; + bFirstHeader = true; + bNotEnoughHeaders = true; + for (const boost::property_tree::ptree::value_type& rValue : aTree.get_child("columns")) + { + sal_Int32 nSize = o3tl::toInt32(rValue.second.get<std::string>("size")); + nSize = o3tl::convert(nSize, o3tl::Length::px, o3tl::Length::twip); + OString aText(rValue.second.get<std::string>("text")); + if (bFirstHeader) + { + CPPUNIT_ASSERT(nSize <= nX); + CPPUNIT_ASSERT_EQUAL("3"_ostr, aText); + bFirstHeader = false; + } + else + { + CPPUNIT_ASSERT(nSize > 0); + CPPUNIT_ASSERT(nPrevious < nSize); + if (nSize > nX + nWidth) + { + bNotEnoughHeaders = false; + break; + } + } + nPrevious = nSize; + } + CPPUNIT_ASSERT(!bNotEnoughHeaders); +} + +void DesktopLOKTest::testHiddenRowHeaders() +{ + LibLODocument_Impl* pDocument = loadDoc("hidden-row.ods"); + + pDocument->pClass->initializeForRendering(pDocument, nullptr); + + tools::Long const nX = 0; + tools::Long const nY = 0; + tools::Long nWidth = 0; + tools::Long nHeight = 0; + pDocument->m_pDocumentClass->getDocumentSize(pDocument, &nWidth, &nHeight); + + std::stringstream aPayload; + aPayload << ".uno:ViewRowColumnHeaders?x=" << nX << "&y=" << nY << "&width=" << nWidth << "&height=" << nHeight; + + boost::property_tree::ptree aTree; + char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, aPayload.str().c_str()); + std::stringstream aStream(pJSON); + CPPUNIT_ASSERT(!aStream.str().empty()); + + boost::property_tree::read_json(aStream, aTree); + free(pJSON); + sal_Int32 nPrevious = 0; + sal_Int32 nIndex = 0; + for (const boost::property_tree::ptree::value_type& rValue : aTree.get_child("rows")) + { + sal_Int32 nSize = o3tl::toInt32(rValue.second.get<std::string>("size")); + + if (nIndex++ == 2) + { + // nSize was 510, nPrevious was 255, i.e. hidden row wasn't reported as 0 height. + CPPUNIT_ASSERT_EQUAL(nPrevious, nSize); + break; + } + nPrevious = nSize; + } +} + +void DesktopLOKTest::testCellCursor() +{ + LibLODocument_Impl* pDocument = loadDoc("search.ods"); + + boost::property_tree::ptree aTree; + + char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:CellCursor?tileWidth=1&tileHeight=1&outputWidth=1&outputHeight=1"); + + std::stringstream aStream(pJSON); + free(pJSON); + CPPUNIT_ASSERT(!aStream.str().empty()); + + boost::property_tree::read_json(aStream, aTree); + + OString aRectangle(aTree.get<std::string>("commandValues")); + // cell cursor geometry + col + row + CPPUNIT_ASSERT_EQUAL("0, 0, 1274, 254, 0, 0"_ostr, aRectangle); +} + +void DesktopLOKTest::testCommandResult() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + + // the postUnoCommand() is supposed to be async, let's test it safely + // [no idea if it is async in reality - most probably we are operating + // under some solar mutex or something anyway ;-) - but...] + TimeValue aTimeValue = { 2 , 0 }; // 2 seconds max + + // nothing is triggered when we have no callback yet, we just time out on + // the condition var. + m_aCommandResultCondition.reset(); + pDocument->pClass->postUnoCommand(pDocument, ".uno:Bold", nullptr, true); + Scheduler::ProcessEventsToIdle(); + m_aCommandResultCondition.wait(aTimeValue); + + CPPUNIT_ASSERT(m_aCommandResult.isEmpty()); + + // but we get some real values when the callback is set up + pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); + + m_aCommandResultCondition.reset(); + pDocument->pClass->postUnoCommand(pDocument, ".uno:Bold", nullptr, true); + Scheduler::ProcessEventsToIdle(); + m_aCommandResultCondition.wait(aTimeValue); + + boost::property_tree::ptree aTree; + std::stringstream aStream((std::string(m_aCommandResult))); + boost::property_tree::read_json(aStream, aTree); + + CPPUNIT_ASSERT_EQUAL(std::string(".uno:Bold"), aTree.get_child("commandName").get_value<std::string>()); + CPPUNIT_ASSERT_EQUAL(true, aTree.get_child("success").get_value<bool>()); +} + +void DesktopLOKTest::testWriterComments() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); + uno::Reference<awt::XReschedule> xToolkit = com::sun::star::awt::Toolkit::create(comphelper::getProcessComponentContext()); + + // Insert a comment at the beginning of the document and wait till the main + // loop grabs the focus, so characters end up in the annotation window. + TimeValue const aTimeValue = {2 , 0}; // 2 seconds max + m_aCommandResultCondition.reset(); + pDocument->pClass->postUnoCommand(pDocument, ".uno:InsertAnnotation", nullptr, true); + Scheduler::ProcessEventsToIdle(); + m_aCommandResultCondition.wait(aTimeValue); + CPPUNIT_ASSERT(!m_aCommandResult.isEmpty()); + xToolkit->reschedule(); + + // Test that we have a comment. + uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY); + uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xTextDocument->getText(), uno::UNO_QUERY); + uno::Reference<container::XEnumeration> xParagraphEnumeration = xParagraphEnumerationAccess->createEnumeration(); + uno::Reference<container::XEnumerationAccess> xParagraph(xParagraphEnumeration->nextElement(), uno::UNO_QUERY); + uno::Reference<container::XEnumeration> xTextPortionEnumeration = xParagraph->createEnumeration(); + uno::Reference<beans::XPropertySet> xTextPortion(xTextPortionEnumeration->nextElement(), uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(OUString("Annotation"), xTextPortion->getPropertyValue("TextPortionType").get<OUString>()); + + // Type "test" and finish editing. + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 't', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'e', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 's', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 't', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, com::sun::star::awt::Key::ESCAPE); + Scheduler::ProcessEventsToIdle(); + + // Test that the typed characters ended up in the right window. + auto xTextField = xTextPortion->getPropertyValue("TextField").get< uno::Reference<beans::XPropertySet> >(); + // This was empty, typed characters ended up in the body text. + CPPUNIT_ASSERT_EQUAL(OUString("test"), xTextField->getPropertyValue("Content").get<OUString>()); +} + +void DesktopLOKTest::testTrackChanges() +{ + // Load a document and create two views. + LibLibreOffice_Impl aOffice; + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + pDocument->pClass->initializeForRendering(pDocument, nullptr); + pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); + pDocument->pClass->createView(pDocument); + pDocument->pClass->initializeForRendering(pDocument, nullptr); + pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); + Scheduler::ProcessEventsToIdle(); + + // Enable track changes and assert that both views get notified. + m_nTrackChanges = 0; + pDocument->pClass->postUnoCommand(pDocument, ".uno:TrackChanges", nullptr, false); + Scheduler::ProcessEventsToIdle(); + // This was 1, only the active view was notified. + CPPUNIT_ASSERT_EQUAL(2, m_nTrackChanges); +} + +void DesktopLOKTest::testSheetOperations() +{ + LibLODocument_Impl* pDocument = loadDoc("sheets.ods"); + + // insert the last sheet + pDocument->pClass->postUnoCommand(pDocument, ".uno:Insert", + "{ \"Name\": { \"type\": \"string\", \"value\": \"LastSheet\" }, \"Index\": { \"type\": \"long\", \"value\": 0 } }", false); + + // insert the first sheet + pDocument->pClass->postUnoCommand(pDocument, ".uno:Insert", + "{ \"Name\": { \"type\": \"string\", \"value\": \"FirstSheet\" }, \"Index\": { \"type\": \"long\", \"value\": 1 } }", false); + + // rename the \"Sheet1\" (2nd now) to \"Renamed\" + pDocument->pClass->postUnoCommand(pDocument, ".uno:Name", + "{ \"Name\": { \"type\": \"string\", \"value\": \"Renamed\" }, \"Index\": { \"type\": \"long\", \"value\": 2 } }", false); + + // delete the \"Sheet2\" (3rd) + pDocument->pClass->postUnoCommand(pDocument, ".uno:Remove", + "{ \"Index\": { \"type\": \"long\", \"value\": 3 } }", false); + + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT_EQUAL(6, pDocument->pClass->getParts(pDocument)); + + std::vector<OString> aExpected = { "FirstSheet"_ostr, "Renamed"_ostr, "Sheet3"_ostr, "Sheet4"_ostr, "Sheet5"_ostr, "LastSheet"_ostr }; + for (int i = 0; i < 6; ++i) + { + char* pPartName = pDocument->pClass->getPartName(pDocument, i); + CPPUNIT_ASSERT_EQUAL(aExpected[i], OString(pPartName)); + free(pPartName); + } +} + +void DesktopLOKTest::testSheetSelections() +{ + LibLODocument_Impl* pDocument = loadDoc("sheets.ods", LOK_DOCTYPE_SPREADSHEET); + pDocument->pClass->initializeForRendering(pDocument, nullptr); + pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); + + /* + * Check if selection data is correct + */ + // Values in twips + int row5 = 1150; + int col1 = 1100; + int const col2 = 2200; + int const col3 = 3300; + int col4 = 4400; + int col5 = 5500; + + // Select row 5 from column 1 through column 5 + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEBUTTONDOWN, + col1, row5, + 1, 1, 0); + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEMOVE, + col2, row5, + 1, 1, 0); + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEMOVE, + col3, row5, + 1, 1, 0); + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEMOVE, + col4, row5, + 1, 1, 0); + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEMOVE, + col5, row5, + 1, 1, 0); + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEBUTTONUP, + col5, row5, + 1, 1, 0); + Scheduler::ProcessEventsToIdle(); + + // Copy the contents and check if matches expected data + { + char* pUsedMimeType = nullptr; + char* pCopiedContent = pDocument->pClass->getTextSelection(pDocument, nullptr, &pUsedMimeType); + std::vector<long> aExpected = {5, 6, 7, 8, 9}; + std::istringstream iss(pCopiedContent); + for (const long nIndex : aExpected) + { + std::string token; + iss >> token; + CPPUNIT_ASSERT_EQUAL(nIndex, strtol(token.c_str(), nullptr, 10)); + } + + free(pUsedMimeType); + free(pCopiedContent); + } + + /* + * Check if clicking inside the selection deselects the whole selection + */ + + // Click at row5, col4 + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEBUTTONDOWN, + col4, row5, + 1, 1, 0); + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEBUTTONUP, + col4, row5, + 1, 1, 0); + Scheduler::ProcessEventsToIdle(); + + // Selected text should get deselected and copying should give us + // content of only one cell, now + { + char* pUsedMimeType = nullptr; + char* pCopiedContent = pDocument->pClass->getTextSelection(pDocument, nullptr, &pUsedMimeType); + std::vector<long> aExpected = { 8 }; + std::istringstream iss(pCopiedContent); + for (const long nIndex : aExpected) + { + std::string token; + iss >> token; + CPPUNIT_ASSERT_EQUAL(nIndex, strtol(token.c_str(), nullptr, 10)); + } + + free(pUsedMimeType); + free(pCopiedContent); + } +} + +void DesktopLOKTest::testSheetDragDrop() +{ + LibLODocument_Impl* pDocument = loadDoc("sheets.ods", LOK_DOCTYPE_SPREADSHEET); + pDocument->pClass->initializeForRendering(pDocument, nullptr); + pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); + + int row01 = 100; + int col01 = 1100; + int col02 = 2200; + int col03 = 3300; + int col05 = 5500; + int col07 = 5700; + + // Select row 01 from column 01 through column 05 + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEBUTTONDOWN, + col01, row01, + 1, 1, 0); + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEMOVE, + col02, row01, + 1, 1, 0); + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEMOVE, + col05, row01, + 1, 1, 0); + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEBUTTONUP, + col05, row01, + 1, 1, 0); + + Scheduler::ProcessEventsToIdle(); + { + SfxViewShell* pViewShell = SfxViewShell::Current(); + SfxViewFrame& rViewFrame = pViewShell->GetViewFrame(); + + OUString sValue; + css::uno::Any aValue; + css::util::URL aURL; + std::unique_ptr<SfxPoolItem> pState; + + aURL.Protocol = ".uno:"; + aURL.Complete = ".uno:Address"; + aURL.Path = "Address"; + aURL.Main = ".uno:Address"; + + rViewFrame.GetBindings().QueryState(rViewFrame.GetBindings().QuerySlotId(aURL), pState); + pState->QueryValue(aValue); + aValue >>= sValue; + CPPUNIT_ASSERT_EQUAL(OUString("Sheet5.A1:E1"), sValue); + } + + // Check selection content + { + char* pMimeType = nullptr; + char* pContent = pDocument->pClass->getTextSelection(pDocument, nullptr, &pMimeType); + std::vector<long> aExpected = {1, 2, 3, 4, 5}; + std::istringstream aContent(pContent); + std::string token; + for (const long nIndex : aExpected) + { + aContent >> token; + CPPUNIT_ASSERT_EQUAL(nIndex, strtol(token.c_str(), nullptr, 10)); + } + + free(pMimeType); + free(pContent); + } + + // drag and drop + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEBUTTONDOWN, + col01, row01, + 1, 1, 0); + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEMOVE, + col02, row01, + 1, 1, 0); + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEMOVE, + col03, row01, + 1, 1, 0); + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEBUTTONUP, + col07, row01, + 1, 1, 0); + + Scheduler::ProcessEventsToIdle(); + { + SfxViewShell* pViewShell = SfxViewShell::Current(); + SfxViewFrame& rViewFrame = pViewShell->GetViewFrame(); + + OUString sValue; + css::uno::Any aValue; + css::util::URL aURL; + std::unique_ptr<SfxPoolItem> pState; + + aURL.Protocol = ".uno:"; + aURL.Complete = ".uno:Address"; + aURL.Path = "Address"; + aURL.Main = ".uno:Address"; + + rViewFrame.GetBindings().QueryState(rViewFrame.GetBindings().QuerySlotId(aURL), pState); + pState->QueryValue(aValue); + aValue >>= sValue; + CPPUNIT_ASSERT_EQUAL(OUString("Sheet5.D1:H1"), sValue); + } + + // Check selection content + { + char* pMimeType = nullptr; + char* pContent = pDocument->pClass->getTextSelection(pDocument, nullptr, &pMimeType); + std::vector<long> aExpected = {1, 2, 3, 4, 5}; + std::istringstream aContent(pContent); + std::string token; + for (const long nIndex : aExpected) + { + aContent >> token; + CPPUNIT_ASSERT_EQUAL(nIndex, strtol(token.c_str(), nullptr, 10)); + } + + free(pMimeType); + free(pContent); + } +} + +namespace { + + void verifyContextMenuStructure(boost::property_tree::ptree& aRoot) + { + for (const auto& aItemPair: aRoot) + { + // This is an array, so no key + CPPUNIT_ASSERT_EQUAL(aItemPair.first, std::string("")); + + boost::property_tree::ptree aItemValue = aItemPair.second; + boost::optional<boost::property_tree::ptree&> aText = aItemValue.get_child_optional("text"); + boost::optional<boost::property_tree::ptree&> aType = aItemValue.get_child_optional("type"); + boost::optional<boost::property_tree::ptree&> aCommand = aItemValue.get_child_optional("command"); + boost::optional<boost::property_tree::ptree&> aSubmenu = aItemValue.get_child_optional("menu"); + boost::optional<boost::property_tree::ptree&> aEnabled = aItemValue.get_child_optional("enabled"); + boost::optional<boost::property_tree::ptree&> aChecktype = aItemValue.get_child_optional("checktype"); + boost::optional<boost::property_tree::ptree&> aChecked = aItemValue.get_child_optional("checked"); + + // type is omnipresent + CPPUNIT_ASSERT( aType ); + + // separator doesn't have any other attribs + if ( aType.get().data() == "separator" ) + { + CPPUNIT_ASSERT( !aText ); + CPPUNIT_ASSERT( !aCommand ); + CPPUNIT_ASSERT( !aSubmenu ); + CPPUNIT_ASSERT( !aEnabled ); + CPPUNIT_ASSERT( !aChecktype ); + CPPUNIT_ASSERT( !aChecked ); + } + else if ( aType.get().data() == "command" ) + { + CPPUNIT_ASSERT( aCommand ); + CPPUNIT_ASSERT( aText ); + } + else if ( aType.get().data() == "menu") + { + CPPUNIT_ASSERT( aSubmenu ); + CPPUNIT_ASSERT( aText ); + verifyContextMenuStructure( aSubmenu.get() ); + } + + if ( aChecktype ) + { + CPPUNIT_ASSERT( aChecktype.get().data() == "radio" || + aChecktype.get().data() == "checkmark" || + aChecktype.get().data() == "auto" ); + + CPPUNIT_ASSERT( aChecked ); + CPPUNIT_ASSERT( aChecked.get().data() == "true" || aChecked.get().data() == "false" ); + } + } + + } + + boost::optional<boost::property_tree::ptree> + getContextMenuItem(boost::property_tree::ptree& aMenu, std::string const & unoSelector) + { + boost::optional<boost::property_tree::ptree> aMenuItem; + for (const auto& aItemPair: aMenu) + { + boost::property_tree::ptree aItemValue = aItemPair.second; + + boost::optional<boost::property_tree::ptree&> aCommand = aItemValue.get_child_optional("command"); + if (aCommand && aCommand.get().data() == unoSelector ) + { + aMenuItem = aItemValue; + break; + } + } + + return aMenuItem; + } + +} // end anonymous namespace + +void DesktopLOKTest::testContextMenuCalc() +{ + LibLODocument_Impl* pDocument = loadDoc("sheet_with_image.ods", LOK_DOCTYPE_SPREADSHEET); + pDocument->pClass->initializeForRendering(pDocument, nullptr); + pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); + + // Values in twips + Point aPointOnImage(1150, 1100); + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEBUTTONDOWN, + aPointOnImage.X(), aPointOnImage.Y(), + 1, 4, 0); + Scheduler::ProcessEventsToIdle(); + + TimeValue const aTimeValue = {2 , 0}; // 2 seconds max + m_aContextMenuCondition.wait(aTimeValue); + + CPPUNIT_ASSERT( !m_aContextMenuResult.empty() ); + boost::optional<boost::property_tree::ptree&> aMenu = m_aContextMenuResult.get_child_optional("menu"); + CPPUNIT_ASSERT( aMenu ); + verifyContextMenuStructure( aMenu.get() ); + + // tests for calc specific context menu + // Cut is enabled + { + boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Cut"); + CPPUNIT_ASSERT(aMenuItem); + + boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled"); + CPPUNIT_ASSERT(aEnabled); + CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("true")); + } + + // Copy is enabled + { + boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Copy"); + CPPUNIT_ASSERT(aMenuItem); + + boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled"); + CPPUNIT_ASSERT(aEnabled); + CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("true")); + } + + // Paste is enabled + { + boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Paste"); + CPPUNIT_ASSERT(aMenuItem); + + boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled"); + CPPUNIT_ASSERT(aEnabled); + CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("true")); + } + + // Remove hyperlink is disabled + { + boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:RemoveHyperlink"); + CPPUNIT_ASSERT(aMenuItem); + + boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled"); + CPPUNIT_ASSERT(aEnabled); + CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false")); + } + + // open hyperlink is disabled + { + boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:OpenHyperlinkOnCursor"); + CPPUNIT_ASSERT(aMenuItem); + + boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled"); + CPPUNIT_ASSERT(aEnabled); + CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false")); + } + + // checkbutton tests + { + boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:AnchorMenu"); + CPPUNIT_ASSERT(aMenuItem); + + boost::optional<boost::property_tree::ptree&> aSubmenu = aMenuItem.get().get_child_optional("menu"); + CPPUNIT_ASSERT(aSubmenu); + + boost::optional<boost::property_tree::ptree> aMenuItemToPage = getContextMenuItem(aSubmenu.get(), ".uno:SetAnchorToPage"); + CPPUNIT_ASSERT(aMenuItemToPage); + + boost::optional<boost::property_tree::ptree> aMenuItemToCell = getContextMenuItem(aSubmenu.get(), ".uno:SetAnchorToCell"); + CPPUNIT_ASSERT(aMenuItemToCell); + + // these are radio buttons + boost::optional<boost::property_tree::ptree&> aChecktypeToPage = aMenuItemToPage.get().get_child_optional("checktype"); + CPPUNIT_ASSERT(aChecktypeToPage); + CPPUNIT_ASSERT_EQUAL(aChecktypeToPage.get().data(), std::string("radio")); + + boost::optional<boost::property_tree::ptree&> aChecktypeToCell = aMenuItemToCell.get().get_child_optional("checktype"); + CPPUNIT_ASSERT(aChecktypeToCell); + CPPUNIT_ASSERT_EQUAL(aChecktypeToCell.get().data(), std::string("radio")); + + // ToPage is checked + boost::optional<boost::property_tree::ptree&> aCheckedToPage = aMenuItemToPage.get().get_child_optional("checked"); + CPPUNIT_ASSERT(aCheckedToPage); + CPPUNIT_ASSERT_EQUAL(aCheckedToPage.get().data(), std::string("true")); + + // ToCell is unchecked + boost::optional<boost::property_tree::ptree&> aCheckedToCell = aMenuItemToCell.get().get_child_optional("checked"); + CPPUNIT_ASSERT(aCheckedToCell); + CPPUNIT_ASSERT_EQUAL(aCheckedToCell.get().data(), std::string("false")); + } +} + +void DesktopLOKTest::testContextMenuWriter() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + pDocument->pClass->initializeForRendering(pDocument, nullptr); + pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); + + Point aRandomPoint(1150, 1100); + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEBUTTONDOWN, + aRandomPoint.X(), aRandomPoint.Y(), + 1, 4, 0); + Scheduler::ProcessEventsToIdle(); + + TimeValue const aTimeValue = {2 , 0}; // 2 seconds max + m_aContextMenuCondition.wait(aTimeValue); + + CPPUNIT_ASSERT( !m_aContextMenuResult.empty() ); + boost::optional<boost::property_tree::ptree&> aMenu = m_aContextMenuResult.get_child_optional("menu"); + CPPUNIT_ASSERT( aMenu ); + verifyContextMenuStructure( aMenu.get() ); + + // tests for writer specific context menu + // Cut is disabled + { + boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Cut"); + CPPUNIT_ASSERT(aMenuItem); + + boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled"); + CPPUNIT_ASSERT(aEnabled); + CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false")); + } + + // Copy is disabled + { + boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Copy"); + CPPUNIT_ASSERT(aMenuItem); + + boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled"); + CPPUNIT_ASSERT(aEnabled); + CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false")); + } + + // Paste is enabled + { + boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Paste"); + CPPUNIT_ASSERT(aMenuItem); + + boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled"); + CPPUNIT_ASSERT(aEnabled); + CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("true")); + } +} + +void DesktopLOKTest::testContextMenuImpress() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_presentation.odp", LOK_DOCTYPE_PRESENTATION); + pDocument->pClass->initializeForRendering(pDocument, nullptr); + pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); + + // random point where we don't hit an underlying comment or text box + Point aRandomPoint(10, 1150); + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEBUTTONDOWN, + aRandomPoint.X(), aRandomPoint.Y(), + 1, 4, 0); + Scheduler::ProcessEventsToIdle(); + + TimeValue const aTimeValue = {2 , 0}; // 2 seconds max + m_aContextMenuCondition.wait(aTimeValue); + + CPPUNIT_ASSERT( !m_aContextMenuResult.empty() ); + boost::optional<boost::property_tree::ptree&> aMenu = m_aContextMenuResult.get_child_optional("menu"); + CPPUNIT_ASSERT( aMenu ); + verifyContextMenuStructure( aMenu.get() ); + + // tests for impress specific context menu + // Cut is disabled + { + boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Cut"); + CPPUNIT_ASSERT(aMenuItem); + + boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled"); + CPPUNIT_ASSERT(aEnabled); + CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false")); + } + + // Copy is disabled + { + boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Copy"); + CPPUNIT_ASSERT(aMenuItem); + + boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled"); + CPPUNIT_ASSERT(aEnabled); + CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false")); + } + + // Paste is enabled + { + boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Paste"); + CPPUNIT_ASSERT(aMenuItem); + + boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled"); + CPPUNIT_ASSERT(aEnabled); + CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("true")); + } + + // SaveBackground is disabled + { + boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:SaveBackground"); + CPPUNIT_ASSERT(aMenuItem); + + boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled"); + CPPUNIT_ASSERT(aEnabled); + CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false")); + } + + // checkbutton tests + { + boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:ShowRuler"); + CPPUNIT_ASSERT(aMenuItem); + + boost::optional<boost::property_tree::ptree&> aChecktype = aMenuItem.get().get_child_optional("checktype"); + CPPUNIT_ASSERT(aChecktype); + CPPUNIT_ASSERT_EQUAL(aChecktype.get().data(), std::string("checkmark")); + + boost::optional<boost::property_tree::ptree&> aChecked = aMenuItem.get().get_child_optional("checked"); + CPPUNIT_ASSERT(aChecked); + CPPUNIT_ASSERT_EQUAL(aChecked.get().data(), std::string("false")); + } + + // Checkbutton tests inside SnapLines submenu + { + boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:SnapLinesMenu"); + CPPUNIT_ASSERT(aMenuItem); + + boost::optional<boost::property_tree::ptree&> aSubmenu = aMenuItem.get().get_child_optional("menu"); + CPPUNIT_ASSERT(aSubmenu); + + boost::optional<boost::property_tree::ptree> aMenuItemHelpVis = getContextMenuItem(aSubmenu.get(), ".uno:HelplinesVisible"); + CPPUNIT_ASSERT(aMenuItemHelpVis); + + boost::optional<boost::property_tree::ptree> aMenuItemHelpUse = getContextMenuItem(aSubmenu.get(), ".uno:HelplinesUse"); + CPPUNIT_ASSERT(aMenuItemHelpUse); + + boost::optional<boost::property_tree::ptree> aMenuItemHelpFront = getContextMenuItem(aSubmenu.get(), ".uno:HelplinesFront"); + CPPUNIT_ASSERT(aMenuItemHelpFront); + + // these are checkmarks + boost::optional<boost::property_tree::ptree&> aChecktypeHelpVis = aMenuItemHelpVis.get().get_child_optional("checktype"); + CPPUNIT_ASSERT(aChecktypeHelpVis); + CPPUNIT_ASSERT_EQUAL(aChecktypeHelpVis.get().data(), std::string("checkmark")); + + boost::optional<boost::property_tree::ptree&> aChecktypeHelpUse = aMenuItemHelpUse.get().get_child_optional("checktype"); + CPPUNIT_ASSERT(aChecktypeHelpUse); + CPPUNIT_ASSERT_EQUAL(aChecktypeHelpUse.get().data(), std::string("checkmark")); + + boost::optional<boost::property_tree::ptree&> aChecktypeHelpFront = aMenuItemHelpFront.get().get_child_optional("checktype"); + CPPUNIT_ASSERT(aChecktypeHelpFront); + CPPUNIT_ASSERT_EQUAL(aChecktypeHelpFront.get().data(), std::string("checkmark")); + + // HelplineVisible is unchecked + boost::optional<boost::property_tree::ptree&> aCheckedHelpVis = aMenuItemHelpVis.get().get_child_optional("checked"); + CPPUNIT_ASSERT(aCheckedHelpVis); + CPPUNIT_ASSERT_EQUAL(aCheckedHelpVis.get().data(), std::string("false")); + + // HelplineUse is checked + boost::optional<boost::property_tree::ptree&> aCheckedHelpUse = aMenuItemHelpUse.get().get_child_optional("checked"); + CPPUNIT_ASSERT(aCheckedHelpUse); + CPPUNIT_ASSERT_EQUAL(aCheckedHelpUse.get().data(), std::string("true")); + + // HelplineFront is checked + boost::optional<boost::property_tree::ptree&> aCheckedHelpFront = aMenuItemHelpFront.get().get_child_optional("checked"); + CPPUNIT_ASSERT(aCheckedHelpFront); + CPPUNIT_ASSERT_EQUAL(aCheckedHelpFront.get().data(), std::string("true")); + } +} + +static void callbackCompressionTest(const int type, const char* payload, void* data) +{ + std::vector<std::tuple<int, std::string>>* notifs = static_cast<std::vector<std::tuple<int, std::string>>*>(data); + notifs->emplace_back(type, std::string(payload ? payload : "(nil)")); +} + +void DesktopLOKTest::testNotificationCompression() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + std::vector<std::tuple<int, std::string>> notifs; + std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, ¬ifs)); + handler->setViewId(SfxLokHelper::getView()); + + handler->queue(LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR, ""_ostr); // 0 + handler->queue(LOK_CALLBACK_TEXT_SELECTION, "15, 25, 15, 10"_ostr); // Superseded. + handler->queue(LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR, ""_ostr); // Should be dropped. + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "15, 25, 15, 10"_ostr); // 1 + handler->queue(LOK_CALLBACK_TEXT_SELECTION, "15, 25, 15, 10"_ostr); // Should be dropped. + handler->queue(LOK_CALLBACK_TEXT_SELECTION, ""_ostr); // Superseded. + handler->queue(LOK_CALLBACK_STATE_CHANGED, ""_ostr); // 2 + handler->queue(LOK_CALLBACK_STATE_CHANGED, ".uno:Bold"_ostr); // 3 + handler->queue(LOK_CALLBACK_STATE_CHANGED, ""_ostr); // 4 + handler->queue(LOK_CALLBACK_MOUSE_POINTER, "text"_ostr); // 5 + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "15, 25, 15, 10"_ostr); // Should be dropped. + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "15, 25, 15, 10"_ostr); // Should be dropped. + handler->queue(LOK_CALLBACK_MOUSE_POINTER, "text"_ostr); // Should be dropped. + handler->queue(LOK_CALLBACK_TEXT_SELECTION_START, "15, 25, 15, 10"_ostr); // Superseded. + handler->queue(LOK_CALLBACK_TEXT_SELECTION_END, "15, 25, 15, 10"_ostr); // Superseded. + handler->queue(LOK_CALLBACK_TEXT_SELECTION, "15, 25, 15, 10"_ostr); // Superseded. + handler->queue(LOK_CALLBACK_TEXT_SELECTION_START, "15, 25, 15, 10"_ostr); // Should be dropped. + handler->queue(LOK_CALLBACK_TEXT_SELECTION_END, "15, 25, 15, 10"_ostr); // Should be dropped. + handler->queue(LOK_CALLBACK_TEXT_SELECTION, ""_ostr); // 7 + handler->queue(LOK_CALLBACK_TEXT_SELECTION_START, "15, 25, 15, 10"_ostr); // 8 + handler->queue(LOK_CALLBACK_TEXT_SELECTION_END, "15, 25, 15, 10"_ostr); // 9 + handler->queue(LOK_CALLBACK_CELL_CURSOR, "15, 25, 15, 10"_ostr); // 10 + handler->queue(LOK_CALLBACK_CURSOR_VISIBLE, ""_ostr); // 11 + handler->queue(LOK_CALLBACK_CELL_CURSOR, "15, 25, 15, 10"_ostr); // Should be dropped. + handler->queue(LOK_CALLBACK_CELL_FORMULA, "blah"_ostr); // 12 + handler->queue(LOK_CALLBACK_SET_PART, "1"_ostr); // 13 + handler->queue(LOK_CALLBACK_STATE_CHANGED, ".uno:AssignLayout=20"_ostr); // Superseded + handler->queue(LOK_CALLBACK_CURSOR_VISIBLE, ""_ostr); // Should be dropped. + handler->queue(LOK_CALLBACK_CELL_FORMULA, "blah"_ostr); // Should be dropped. + handler->queue(LOK_CALLBACK_SET_PART, "1"_ostr); // Should be dropped. + handler->queue(LOK_CALLBACK_STATE_CHANGED, ".uno:AssignLayout=1"_ostr); // 14 + + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(14), notifs.size()); + + size_t i = 0; + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string(""), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("15, 25, 15, 10"), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_STATE_CHANGED), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string(""), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_STATE_CHANGED), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string(".uno:Bold"), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_STATE_CHANGED), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string(""), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_MOUSE_POINTER), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("text"), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_TEXT_SELECTION), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string(""), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_TEXT_SELECTION_START), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("15, 25, 15, 10"), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_TEXT_SELECTION_END), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("15, 25, 15, 10"), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_CELL_CURSOR), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("15, 25, 15, 10"), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_CURSOR_VISIBLE), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string(""), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_CELL_FORMULA), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("blah"), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_SET_PART), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("1"), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_STATE_CHANGED), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string(".uno:AssignLayout=1"), std::get<1>(notifs[i++])); +} + +void DesktopLOKTest::testTileInvalidationCompression() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + + comphelper::LibreOfficeKit::setPartInInvalidation(true); + comphelper::ScopeGuard aGuard([]() + { + comphelper::LibreOfficeKit::setPartInInvalidation(false); + }); + + // Single part merging + { + std::vector<std::tuple<int, std::string>> notifs; + std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, ¬ifs)); + handler->setViewId(SfxLokHelper::getView()); + + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 239, 0, 0"_ostr); + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 239, 0, 0"_ostr); + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "-100, -50, 500, 650, 0, 0"_ostr); + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, -32767, -32767, 0, 0"_ostr); + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "100, 100, 200, 200, 0, 0"_ostr); + + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), notifs.size()); + + size_t i = 0; + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 400, 600, 0, 0"), std::get<1>(notifs[i++])); + } + + // Part Number + { + std::vector<std::tuple<int, std::string>> notifs; + std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, ¬ifs)); + handler->setViewId(SfxLokHelper::getView()); + + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 239, 0, 0"_ostr); + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 200, 200, 1, 0"_ostr); // Different part + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 0, 0, 2, 0"_ostr); // Invalid + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "-121, -121, 200, 200, 0, 0"_ostr); // Inside first + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, -32767, -32767, 1, 0"_ostr); // Invalid + + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), notifs.size()); + + size_t i = 0; + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 200, 200, 1, 0"), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 239, 239, 0, 0"), std::get<1>(notifs[i++])); + } + + // All Parts + { + std::vector<std::tuple<int, std::string>> notifs; + std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, ¬ifs)); + handler->setViewId(SfxLokHelper::getView()); + + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 239, 0, 0"_ostr); // 0 + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 200, 200, 1, 0"_ostr); // 1: Different part + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 0, 0, -1, 0"_ostr); // Invalid + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "-121, -121, 200, 200, -1, 0"_ostr); // 0: All parts + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, -32767, -32767, -1, 0"_ostr); // Invalid + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "-100, -100, 1200, 1200, -1, 0"_ostr); // 0: All parts + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 239, 3, 0"_ostr); // Overlapped + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "1000, 1000, 1239, 1239, 2, 0"_ostr); // 1: Unique region + + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), notifs.size()); + + size_t i = 0; + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 1100, 1100, -1, 0"), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("1000, 1000, 1239, 1239, 2, 0"), std::get<1>(notifs[i++])); + } + + // All Parts (partial) + { + std::vector<std::tuple<int, std::string>> notifs; + std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, ¬ifs)); + handler->setViewId(SfxLokHelper::getView()); + + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 200, 200, 0, 0"_ostr); // 0 + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 100, 100, 1, 0"_ostr); // 1: Different part + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 0, 0, -1, 0"_ostr); // Invalid + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "150, 150, 50, 50, -1, 0"_ostr); // 2: All-parts + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, -32767, -32767, -1, 0"_ostr); // Invalid + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "150, 150, 40, 40, 3, 0"_ostr); // Overlapped w/ 2 + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 200, 200, 4, 0"_ostr); // 3: Unique + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "1000, 1000, 1239, 1239, 1, 0"_ostr); // 4: Unique + + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(5), notifs.size()); + + size_t i = 0; + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 200, 200, 0, 0"), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 100, 100, 1, 0"), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("150, 150, 50, 50, -1, 0"), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 200, 200, 4, 0"), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("1000, 1000, 1239, 1239, 1, 0"), std::get<1>(notifs[i++])); + } + + // Merge with "EMPTY" + { + std::vector<std::tuple<int, std::string>> notifs; + std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, ¬ifs)); + handler->setViewId(SfxLokHelper::getView()); + + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 239, 0, 0"_ostr); + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "EMPTY, 0, 0"_ostr); + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 240, 0, 0"_ostr); + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "-121, -121, 300, 300, 0, 0"_ostr); + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, -32767, -32767, 0, 0"_ostr); + + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), notifs.size()); + + size_t i = 0; + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("EMPTY, 0, 0"), std::get<1>(notifs[i++])); + } +} + +void DesktopLOKTest::testPartInInvalidation() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + // No part in invalidation: merge. + { + std::vector<std::tuple<int, std::string>> notifs; + std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, ¬ifs)); + handler->setViewId(SfxLokHelper::getView()); + + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "10, 10, 20, 10"_ostr); + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "20, 10, 20, 10"_ostr); + + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), notifs.size()); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[0])); + CPPUNIT_ASSERT_EQUAL(std::string("10, 10, 30, 10"), std::get<1>(notifs[0])); + } + // No part in invalidation: don't merge. + { + std::vector<std::tuple<int, std::string>> notifs; + std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, ¬ifs)); + handler->setViewId(SfxLokHelper::getView()); + + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "10, 10, 20, 10"_ostr); + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "40, 10, 20, 10"_ostr); + + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), notifs.size()); + } + + // Part in invalidation, intersection and parts match -> merge. + { + comphelper::LibreOfficeKit::setPartInInvalidation(true); + comphelper::ScopeGuard aGuard([]() + { + comphelper::LibreOfficeKit::setPartInInvalidation(false); + }); + + std::vector<std::tuple<int, std::string>> notifs; + std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, ¬ifs)); + handler->setViewId(SfxLokHelper::getView()); + + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "10, 10, 20, 10, 0, 0"_ostr); + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "20, 10, 20, 10, 0, 0"_ostr); + + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), notifs.size()); + } + // Part in invalidation, intersection and parts don't match -> don't merge. + { + comphelper::LibreOfficeKit::setPartInInvalidation(true); + comphelper::ScopeGuard aGuard([]() + { + comphelper::LibreOfficeKit::setPartInInvalidation(false); + }); + + std::vector<std::tuple<int, std::string>> notifs; + std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, ¬ifs)); + handler->setViewId(SfxLokHelper::getView()); + + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "10, 10, 20, 10, 0, 0"_ostr); + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "20, 10, 20, 10, 1, 0"_ostr); + + Scheduler::ProcessEventsToIdle(); + + // This failed as RectangleAndPart::Create() always assumed no part in + // payload, so this was merged -> it was 1. + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), notifs.size()); + } +} + +static void callbackBinaryCallbackTest(const int type, const char* payload, void* data) +{ + std::vector<std::tuple<int, std::string>>* notifs = static_cast<std::vector<std::tuple<int, std::string>>*>(data); + notifs->emplace_back(type, std::string(payload ? payload : "(nil)")); +} + +void DesktopLOKTest::testBinaryCallback() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + + const tools::Rectangle rect1(Point(10,15),Size(20,25)); + const std::string rect1String(rect1.toString()); + // Verify that using queue() and libreOfficeKitViewInvalidateTilesCallback() has the same result. + { + std::vector<std::tuple<int, std::string>> notifs; + std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackBinaryCallbackTest, ¬ifs)); + handler->setViewId(SfxLokHelper::getView()); + + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, OString(rect1String)); + + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), notifs.size()); + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[0])); + CPPUNIT_ASSERT_EQUAL(rect1String, std::get<1>(notifs[0])); + } + { + std::vector<std::tuple<int, std::string>> notifs; + std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackBinaryCallbackTest, ¬ifs)); + handler->setViewId(SfxLokHelper::getView()); + + handler->libreOfficeKitViewInvalidateTilesCallback(&rect1, INT_MIN, 0); + + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), notifs.size()); + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[0])); + CPPUNIT_ASSERT_EQUAL(rect1String, std::get<1>(notifs[0])); + } + // Verify that the "EMPTY" invalidation gets converted properly. + { + std::vector<std::tuple<int, std::string>> notifs; + std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackBinaryCallbackTest, ¬ifs)); + handler->setViewId(SfxLokHelper::getView()); + + handler->libreOfficeKitViewInvalidateTilesCallback(nullptr, INT_MIN, 0); + + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), notifs.size()); + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[0])); + CPPUNIT_ASSERT_EQUAL(std::string("EMPTY"), std::get<1>(notifs[0])); + } +} + +void DesktopLOKTest::testInput() +{ + // Load a Writer document, enable change recording and press a key. + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + + Scheduler::ProcessEventsToIdle(); // Get focus & other bits setup. + + pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, "far"); + pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, "far"); + pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, " "); + pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, " "); + pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, "beyond"); + pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, "beyond"); + pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, " "); + pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, " "); + // Mis-spelled ... + pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, "kovely"); + pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, "kovely"); + // Remove it again + pDocument->pClass->removeTextContext(pDocument, 0, 6, 0); + // Replace it with lovely + pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, "lovely"); + pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, "lovely"); + pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, " "); + pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, " "); + + // get the text ... + pDocument->pClass->postUnoCommand(pDocument, ".uno:SelectAll", nullptr, false); + Scheduler::ProcessEventsToIdle(); + char* pText = pDocument->pClass->getTextSelection(pDocument, "text/plain;charset=utf-8", nullptr); + CPPUNIT_ASSERT(pText != nullptr); + CPPUNIT_ASSERT_EQUAL("far beyond lovely "_ostr, OString(pText)); + free(pText); +} + +void DesktopLOKTest::testRedlineWriter() +{ + // Load a Writer document, enable change recording and press a key. + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + uno::Reference<beans::XPropertySet> xPropertySet(mxComponent, uno::UNO_QUERY); + xPropertySet->setPropertyValue("RecordChanges", uno::Any(true)); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 't', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 't', 0); + Scheduler::ProcessEventsToIdle(); + + // Get redline info. + boost::property_tree::ptree aTree; + char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:AcceptTrackedChanges"); + std::stringstream aStream(pJSON); + free(pJSON); + CPPUNIT_ASSERT(!aStream.str().empty()); + boost::property_tree::read_json(aStream, aTree); + // Make sure that pressing a key creates exactly one redline. + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aTree.get_child("redlines").size()); + + for (const boost::property_tree::ptree::value_type& rRedline : aTree.get_child("redlines")) + // This failed with boost::property_tree::ptree_bad_path, as there were no description field. + CPPUNIT_ASSERT_EQUAL(std::string("Insert \xE2\x80\x9Ct\xE2\x80\x9D"), rRedline.second.get<std::string>("description")); + // U+201C LEFT DOUBLE QUOTATION MARK, U+201D RIGHT DOUBLE QUOTATION + // MARK +} + +void DesktopLOKTest::testRedlineCalc() +{ + // Load a Writer document, enable change recording and press a key. + LibLODocument_Impl* pDocument = loadDoc("sheets.ods"); + uno::Reference<beans::XPropertySet> xPropertySet(mxComponent, uno::UNO_QUERY); + xPropertySet->setPropertyValue("RecordChanges", uno::Any(true)); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 't', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 't', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, KEY_RETURN); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 0, KEY_RETURN); + Scheduler::ProcessEventsToIdle(); + + // Get redline info. + boost::property_tree::ptree aTree; + char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:AcceptTrackedChanges"); + std::stringstream aStream(pJSON); + free(pJSON); + CPPUNIT_ASSERT(!aStream.str().empty()); + boost::property_tree::read_json(aStream, aTree); + // Make sure that pressing a key creates exactly one redline. + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aTree.get_child("redlines").size()); + + for (const boost::property_tree::ptree::value_type& rRedline : aTree.get_child("redlines")) + // This failed with boost::property_tree::ptree_bad_path, as there were no description field. + CPPUNIT_ASSERT_EQUAL(std::string("Cell B4 changed from '5' to 't'"), rRedline.second.get<std::string>("description")); +} + +namespace { + +class ViewCallback +{ + LibLODocument_Impl* mpDocument; + int mnView; +public: + OString m_aCellFormula; + int m_nTableSelectionCount; + int m_nColorPaletteCallbackCount = 0; + bool m_bEmptyTableSelection; + bool m_bTilesInvalidated; + bool m_bZeroCursor; + tools::Rectangle m_aOwnCursor; + boost::property_tree::ptree m_aCommentCallbackResult; + boost::property_tree::ptree m_aColorPaletteCallbackResult; + + ViewCallback(LibLODocument_Impl* pDocument) + : mpDocument(pDocument), + m_nTableSelectionCount(0), + m_bEmptyTableSelection(false), + m_bTilesInvalidated(false), + m_bZeroCursor(false) + { + mnView = SfxLokHelper::getView(); + mpDocument->m_pDocumentClass->registerCallback(pDocument, &ViewCallback::callback, this); + } + + ~ViewCallback() + { + mpDocument->m_pDocumentClass->setView(mpDocument, mnView); + mpDocument->m_pDocumentClass->registerCallback(mpDocument, nullptr, nullptr); + } + + static void callback(int nType, const char* pPayload, void* pData) + { + static_cast<ViewCallback*>(pData)->callbackImpl(nType, pPayload); + } + + void callbackImpl(int nType, const char* pPayload) + { + OString aPayload(pPayload); + switch (nType) + { + case LOK_CALLBACK_INVALIDATE_TILES: + { + m_bTilesInvalidated = true; + } + break; + case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR: + { + uno::Sequence<OUString> aSeq = comphelper::string::convertCommaSeparated(OUString::fromUtf8(aPayload)); + if (std::string_view("EMPTY") == pPayload) + return; + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(4), aSeq.getLength()); + m_aOwnCursor.SetLeft(aSeq[0].toInt32()); + m_aOwnCursor.SetTop(aSeq[1].toInt32()); + m_aOwnCursor.setWidth(aSeq[2].toInt32()); + m_aOwnCursor.setHeight(aSeq[3].toInt32()); + + if (m_aOwnCursor.Left() == 0 && m_aOwnCursor.Top() == 0) + m_bZeroCursor = true; + } + break; + case LOK_CALLBACK_COMMENT: + { + m_aCommentCallbackResult.clear(); + std::stringstream aStream(pPayload); + boost::property_tree::read_json(aStream, m_aCommentCallbackResult); + m_aCommentCallbackResult = m_aCommentCallbackResult.get_child("comment"); + } + break; + break; + case LOK_CALLBACK_CELL_FORMULA: + { + m_aCellFormula = aPayload; + } + break; + case LOK_CALLBACK_TABLE_SELECTED: + { + m_bEmptyTableSelection = (std::string(pPayload).compare("{ }") == 0); + ++m_nTableSelectionCount; + } + break; + case LOK_CALLBACK_COLOR_PALETTES: + { + m_aColorPaletteCallbackResult.clear(); + std::stringstream aStream(pPayload); + boost::property_tree::read_json(aStream, m_aColorPaletteCallbackResult); + ++m_nColorPaletteCallbackCount; + } + break; + } + } +}; + +} + +void DesktopLOKTest::testPaintPartTile() +{ + // Load an impress doc of 2 slides. +// ViewCallback aView1; +// ViewCallback aView2; + LibLODocument_Impl* pDocument = loadDoc("2slides.odp"); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); +// pDocument->m_pDocumentClass->registerCallback(pDocument, &ViewCallback::callback, &aView1); + int nView1 = pDocument->m_pDocumentClass->getView(pDocument); + + // Create a second view. + pDocument->m_pDocumentClass->createView(pDocument); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); +// pDocument->m_pDocumentClass->registerCallback(pDocument, &ViewCallback::callback, &aView2); + + // Go to the second slide in the second view. + pDocument->m_pDocumentClass->setPart(pDocument, 1); + + // Switch back to the first view and start typing. + pDocument->m_pDocumentClass->setView(pDocument, nView1); + pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, awt::Key::TAB); + pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 0, awt::Key::TAB); + pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'x', 0); + pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 'x', 0); + Scheduler::ProcessEventsToIdle(); + + // Call paintPartTile() to paint the second part (in whichever view it finds suitable for this). + unsigned char pPixels[256 * 256 * 4]; + pDocument->m_pDocumentClass->paintPartTile(pDocument, pPixels, 1, 0, 256, 256, 0, 0, 256, 256); + + // Type again. + Scheduler::ProcessEventsToIdle(); +// aView1.m_bTilesInvalidated = false; + pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'x', 0); + pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 'x', 0); + Scheduler::ProcessEventsToIdle(); + // This failed: paintPartTile() (as a side-effect) ended the text edit of + // the first view, so there were no invalidations. + //CPPUNIT_ASSERT(aView1.m_bTilesInvalidated); +} + +void DesktopLOKTest::testPaintPartTileDifferentSchemes() +{ + Color aDarkColor(0x1c, 0x1c, 0x1c); + + // Add a minimal dark scheme + { + svtools::EditableColorConfig aColorConfig; + svtools::ColorConfigValue aValue; + aValue.bIsVisible = true; + aValue.nColor = aDarkColor; + aColorConfig.SetColorValue(svtools::DOCCOLOR, aValue); + aColorConfig.AddScheme(u"Dark"_ustr); + } + + // Add a minimal light scheme + { + svtools::EditableColorConfig aColorConfig; + svtools::ColorConfigValue aValue; + aValue.bIsVisible = true; + aValue.nColor = COL_WHITE; + aColorConfig.SetColorValue(svtools::DOCCOLOR, aValue); + aColorConfig.AddScheme(u"Light"_ustr); + } + + // This view will default to light scheme + LibLODocument_Impl* pDocument = loadDoc("2slides.odp"); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + int nView1 = pDocument->m_pDocumentClass->getView(pDocument); + + // Create a second view + pDocument->m_pDocumentClass->createView(pDocument); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + + // Go to the second slide in the second view + pDocument->m_pDocumentClass->setPart(pDocument, 1); + + // Set to dark scheme + { + uno::Sequence<beans::PropertyValue> aPropertyValues = comphelper::InitPropertySequence( + { + { "NewTheme", uno::Any(OUString("Dark")) }, + } + ); + dispatchCommand(mxComponent, ".uno:ChangeTheme", aPropertyValues); + } + + constexpr int nCanvasWidth = 256; + constexpr int nCanvasHeight = 256; + + // Just a random pixel in the middle of the canvas + constexpr int nPixelX = 128; + constexpr int nPixelY = 128 * nCanvasWidth; + + std::array<sal_uInt8, nCanvasWidth * nCanvasHeight * 4> aPixels; + + // Both parts should be painted with dark scheme + pDocument->m_pDocumentClass->paintPartTile(pDocument, aPixels.data(), 0, 0, nCanvasWidth, nCanvasHeight, 0, 0, nCanvasWidth, nCanvasHeight); + Color aPixel(aPixels[nPixelX + nPixelY + 0], aPixels[nPixelX + nPixelY + 1], aPixels[nPixelX + nPixelY + 2]); + CPPUNIT_ASSERT_EQUAL(aDarkColor, aPixel); + + pDocument->m_pDocumentClass->paintPartTile(pDocument, aPixels.data(), 0, 0, nCanvasWidth, nCanvasHeight, 0, 0, nCanvasWidth, nCanvasHeight); + aPixel = Color(aPixels[nPixelX + nPixelY + 0], aPixels[nPixelX + nPixelY + 1], aPixels[nPixelX + nPixelY + 2]); + CPPUNIT_ASSERT_EQUAL(aDarkColor, aPixel); + + // Switch back to first view + pDocument->m_pDocumentClass->setView(pDocument, nView1); + + // Both parts should be painted with light scheme + pDocument->m_pDocumentClass->paintPartTile(pDocument, aPixels.data(), 0, 0, nCanvasWidth, nCanvasHeight, 0, 0, nCanvasWidth, nCanvasHeight); + aPixel = Color(aPixels[nPixelX + nPixelY + 0], aPixels[nPixelX + nPixelY + 1], aPixels[nPixelX + nPixelY + 2]); + CPPUNIT_ASSERT_EQUAL(COL_WHITE, aPixel); + + pDocument->m_pDocumentClass->paintPartTile(pDocument, aPixels.data(), 0, 0, nCanvasWidth, nCanvasHeight, 0, 0, nCanvasWidth, nCanvasHeight); + aPixel = Color(aPixels[nPixelX + nPixelY + 0], aPixels[nPixelX + nPixelY + 1], aPixels[nPixelX + nPixelY + 2]); + CPPUNIT_ASSERT_EQUAL(COL_WHITE, aPixel); +} + +#if HAVE_MORE_FONTS +void DesktopLOKTest::testGetFontSubset() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + OUString aFontName = rtl::Uri::encode( + OUString("Liberation Sans"), + rtl_UriCharClassRelSegment, + rtl_UriEncodeKeepEscapes, + RTL_TEXTENCODING_UTF8 + ); + OString aCommand = ".uno:FontSubset&name=" + OUStringToOString(aFontName, RTL_TEXTENCODING_UTF8); + boost::property_tree::ptree aTree; + char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, aCommand.getStr()); + std::stringstream aStream(pJSON); + boost::property_tree::read_json(aStream, aTree); + CPPUNIT_ASSERT( !aTree.empty() ); + CPPUNIT_ASSERT_EQUAL( std::string(".uno:FontSubset"), aTree.get_child("commandName").get_value<std::string>() ); + boost::property_tree::ptree aValues = aTree.get_child("commandValues"); + CPPUNIT_ASSERT( !aValues.empty() ); + free(pJSON); +} +#endif + +void DesktopLOKTest::testCommentsWriter() +{ + // Disable tiled rendering for comments + comphelper::LibreOfficeKit::setTiledAnnotations(false); + + LibLODocument_Impl* pDocument = loadDoc("comments.odt"); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, nullptr); + tools::Long nWidth, nHeight; + pDocument->m_pDocumentClass->getDocumentSize(pDocument, &nWidth, &nHeight); + + // Document width alongwith without sidebar comes to be < 13000 + CPPUNIT_ASSERT( nWidth < 13000 ); + + // Can we get all the comments using .uno:ViewAnnotations command ? + boost::property_tree::ptree aTree; + char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:ViewAnnotations"); + std::stringstream aStream(pJSON); + free(pJSON); + CPPUNIT_ASSERT(!aStream.str().empty()); + boost::property_tree::read_json(aStream, aTree); + // There are 3 comments in the document already + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), aTree.get_child("comments").size()); + + int nComment2Id = 0; + // Check if all comment fields have valid data + for (const auto& rComment : aTree.get_child("comments")) + { + CPPUNIT_ASSERT(rComment.second.get<int>("id") > 0); + CPPUNIT_ASSERT(!rComment.second.get<std::string>("author").empty()); + CPPUNIT_ASSERT(!rComment.second.get<std::string>("text").empty()); + // Has a valid iso 8601 date time string + css::util::DateTime aDateTime; + OUString aDateTimeString = OUString::createFromAscii(rComment.second.get<std::string>("dateTime")); + CPPUNIT_ASSERT(utl::ISO8601parseDateTime(aDateTimeString, aDateTime)); + + // This comment has a marked text range + if (rComment.second.get<std::string>("text") == "Comment 2") + { + CPPUNIT_ASSERT(!rComment.second.get<std::string>("textRange").empty()); + nComment2Id = rComment.second.get<int>("id"); + } + // This is a reply comment + else if (rComment.second.get<std::string>("text") == "Reply to Comment 2") + { + CPPUNIT_ASSERT_EQUAL(nComment2Id, rComment.second.get<int>("parentId")); + } + } + + comphelper::LibreOfficeKit::setTiledAnnotations(true); +} + + +void DesktopLOKTest::testCommentsCalc() +{ + // Disable tiled rendering for comments + comphelper::LibreOfficeKit::setTiledAnnotations(false); + + LibLODocument_Impl* pDocument = loadDoc("sheets.ods"); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, nullptr); + + // Can we get all the comments using .uno:ViewAnnotations command ? + boost::property_tree::ptree aTree; + char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:ViewAnnotations"); + std::stringstream aStream(pJSON); + free(pJSON); + CPPUNIT_ASSERT(!aStream.str().empty()); + boost::property_tree::read_json(aStream, aTree); + // There are 2 comments in the document already + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), aTree.get_child("comments").size()); + + // Check if all comment fields have valid data + int nIdx = 0; + for (const auto& rComment : aTree.get_child("comments")) + { + switch(nIdx) + { + case 0: + { + CPPUNIT_ASSERT_EQUAL(std::string("4"), rComment.second.get<std::string>("tab")); + CPPUNIT_ASSERT_EQUAL(std::string("Comment1"), rComment.second.get<std::string>("text")); + CPPUNIT_ASSERT_EQUAL(std::string("6 14 6 14"), rComment.second.get<std::string>("cellRange")); + } + break; + case 1: + { + CPPUNIT_ASSERT_EQUAL(std::string("4"), rComment.second.get<std::string>("tab")); + CPPUNIT_ASSERT_EQUAL(std::string("Comment2"), rComment.second.get<std::string>("text")); + CPPUNIT_ASSERT_EQUAL(std::string("7 17 7 17"), rComment.second.get<std::string>("cellRange")); + } + break; + } + + ++nIdx; + } + + // We checked all the comments + CPPUNIT_ASSERT_EQUAL(2, nIdx); + + comphelper::LibreOfficeKit::setTiledAnnotations(true); +} + + +void DesktopLOKTest::testCommentsImpress() +{ + // Disable tiled rendering for comments + comphelper::LibreOfficeKit::setTiledAnnotations(false); + + LibLODocument_Impl* pDocument = loadDoc("blank_presentation.odp"); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, nullptr); + + // Can we get all the comments using .uno:ViewAnnotations command ? + boost::property_tree::ptree aTree; + char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:ViewAnnotations"); + std::stringstream aStream(pJSON); + free(pJSON); + CPPUNIT_ASSERT(!aStream.str().empty()); + boost::property_tree::read_json(aStream, aTree); + // There are 2 comments in the document already + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), aTree.get_child("comments").size()); + + // Check if all comment fields have valid data + int nIdx = 0; + for (const auto& rComment : aTree.get_child("comments")) + { + switch(nIdx) + { + case 0: + { + CPPUNIT_ASSERT(rComment.second.get<int>("id") > 0); + CPPUNIT_ASSERT_EQUAL(std::string("This is comment1"), rComment.second.get<std::string>("text")); + CPPUNIT_ASSERT_EQUAL(std::string("LOK User1"), rComment.second.get<std::string>("author")); + css::util::DateTime aDateTime; + OUString aDateTimeString = OUString::createFromAscii(rComment.second.get<std::string>("dateTime")); + CPPUNIT_ASSERT(utl::ISO8601parseDateTime(aDateTimeString, aDateTime)); + } + break; + case 1: + { + CPPUNIT_ASSERT(rComment.second.get<int>("id") > 0); + CPPUNIT_ASSERT_EQUAL(std::string("This is comment2"), rComment.second.get<std::string>("text")); + CPPUNIT_ASSERT_EQUAL(std::string("LOK User2"), rComment.second.get<std::string>("author")); + css::util::DateTime aDateTime; + OUString aDateTimeString = OUString::createFromAscii(rComment.second.get<std::string>("dateTime")); + CPPUNIT_ASSERT(utl::ISO8601parseDateTime(aDateTimeString, aDateTime)); + } + break; + } + + ++nIdx; + } + + // We checked all the comments + CPPUNIT_ASSERT_EQUAL(2, nIdx); + + comphelper::LibreOfficeKit::setTiledAnnotations(true); +} + +void DesktopLOKTest::testCommentsCallbacksWriter() +{ + // Comments callback are emitted only if tiled annotations are off + comphelper::LibreOfficeKit::setTiledAnnotations(false); + LibLODocument_Impl* pDocument = loadDoc("comments.odt"); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + ViewCallback aView1(pDocument); + pDocument->m_pDocumentClass->createView(pDocument); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + ViewCallback aView2(pDocument); + + // Add a new comment + OString aCommandArgs("{ \"Text\": { \"type\": \"string\", \"value\": \"Additional comment\" }, \"Author\": { \"type\": \"string\", \"value\": \"LOK User1\" } }"_ostr); + pDocument->pClass->postUnoCommand(pDocument, ".uno:InsertAnnotation", aCommandArgs.getStr(), false); + Scheduler::ProcessEventsToIdle(); + + // We received a LOK_CALLBACK_COMMENT callback with comment 'Add' action + CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView1.m_aCommentCallbackResult.get<std::string>("action")); + CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView2.m_aCommentCallbackResult.get<std::string>("action")); + int nCommentId1 = aView1.m_aCommentCallbackResult.get<int>("id"); + + // Reply to a comment just added + aCommandArgs = "{ \"Id\": { \"type\": \"string\", \"value\": \"" + OString::number(nCommentId1) + "\" }, \"Text\": { \"type\": \"string\", \"value\": \"Reply comment\" } }"; + pDocument->pClass->postUnoCommand(pDocument, ".uno:ReplyComment", aCommandArgs.getStr(), false); + Scheduler::ProcessEventsToIdle(); + + // We received a LOK_CALLBACK_COMMENT callback with comment 'Add' action and linked to its parent comment + CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView1.m_aCommentCallbackResult.get<std::string>("action")); + CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView2.m_aCommentCallbackResult.get<std::string>("action")); + CPPUNIT_ASSERT_EQUAL(nCommentId1, aView1.m_aCommentCallbackResult.get<int>("parentId")); + CPPUNIT_ASSERT_EQUAL(nCommentId1, aView2.m_aCommentCallbackResult.get<int>("parentId")); + CPPUNIT_ASSERT_EQUAL(std::string("Reply comment"), aView1.m_aCommentCallbackResult.get<std::string>("text")); + CPPUNIT_ASSERT_EQUAL(std::string("Reply comment"), aView2.m_aCommentCallbackResult.get<std::string>("text")); + int nCommentId2 = aView1.m_aCommentCallbackResult.get<int>("id"); + + // Edit the previously added comment + aCommandArgs = "{ \"Id\": { \"type\": \"string\", \"value\": \"" + OString::number(nCommentId2) + "\" }, \"Text\": { \"type\": \"string\", \"value\": \"Edited comment\" } }"; + pDocument->pClass->postUnoCommand(pDocument, ".uno:EditAnnotation", aCommandArgs.getStr(), false); + Scheduler::ProcessEventsToIdle(); + + // We received a LOK_CALLBACK_COMMENT callback with comment 'Modify' action + CPPUNIT_ASSERT_EQUAL(std::string("Modify"), aView1.m_aCommentCallbackResult.get<std::string>("action")); + CPPUNIT_ASSERT_EQUAL(std::string("Modify"), aView2.m_aCommentCallbackResult.get<std::string>("action")); + // parent is unchanged still + CPPUNIT_ASSERT_EQUAL(nCommentId1, aView1.m_aCommentCallbackResult.get<int>("parentId")); + CPPUNIT_ASSERT_EQUAL(nCommentId1, aView2.m_aCommentCallbackResult.get<int>("parentId")); + CPPUNIT_ASSERT_EQUAL(std::string("Edited comment"), aView1.m_aCommentCallbackResult.get<std::string>("text")); + CPPUNIT_ASSERT_EQUAL(std::string("Edited comment"), aView2.m_aCommentCallbackResult.get<std::string>("text")); + + // Delete the reply comment just added + aCommandArgs = "{ \"Id\": { \"type\": \"string\", \"value\": \"" + OString::number(nCommentId2) + "\" } }"; + pDocument->pClass->postUnoCommand(pDocument, ".uno:DeleteComment", aCommandArgs.getStr(), false); + Scheduler::ProcessEventsToIdle(); + + // We received a LOK_CALLBACK_COMMENT callback with comment 'Remove' action + CPPUNIT_ASSERT_EQUAL(std::string("Remove"), aView1.m_aCommentCallbackResult.get<std::string>("action")); + CPPUNIT_ASSERT_EQUAL(std::string("Remove"), aView2.m_aCommentCallbackResult.get<std::string>("action")); + CPPUNIT_ASSERT_EQUAL(nCommentId2, aView1.m_aCommentCallbackResult.get<int>("id")); + CPPUNIT_ASSERT_EQUAL(nCommentId2, aView2.m_aCommentCallbackResult.get<int>("id")); + + // Reply to nCommentId1 again + aCommandArgs = "{ \"Id\": { \"type\": \"string\", \"value\": \"" + OString::number(nCommentId1) + "\" }, \"Text\": { \"type\": \"string\", \"value\": \"Reply comment again\" } }"; + pDocument->pClass->postUnoCommand(pDocument, ".uno:ReplyComment", aCommandArgs.getStr(), false); + Scheduler::ProcessEventsToIdle(); + + // We received a LOK_CALLBACK_COMMENT callback with comment 'Add' action and linked to its parent comment + CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView1.m_aCommentCallbackResult.get<std::string>("action")); + CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView2.m_aCommentCallbackResult.get<std::string>("action")); + CPPUNIT_ASSERT_EQUAL(nCommentId1, aView1.m_aCommentCallbackResult.get<int>("parentId")); + CPPUNIT_ASSERT_EQUAL(nCommentId1, aView2.m_aCommentCallbackResult.get<int>("parentId")); + CPPUNIT_ASSERT_EQUAL(std::string("Reply comment again"), aView1.m_aCommentCallbackResult.get<std::string>("text")); + CPPUNIT_ASSERT_EQUAL(std::string("Reply comment again"), aView2.m_aCommentCallbackResult.get<std::string>("text")); + + // .uno:ViewAnnotations returns total of 5 comments + boost::property_tree::ptree aTree; + char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:ViewAnnotations"); + std::stringstream aStream(pJSON); + free(pJSON); + CPPUNIT_ASSERT(!aStream.str().empty()); + boost::property_tree::read_json(aStream, aTree); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(5), aTree.get_child("comments").size()); +} + +namespace +{ + +void addParameter(tools::JsonWriter& rJson, const char* sName, std::string_view type, std::string_view value) +{ + auto testNode = rJson.startNode(sName); + rJson.put("type", type); + rJson.put("value", value); +} + +} + +void DesktopLOKTest::testCommentsAddEditDeleteDraw() +{ + // Comments callback are emitted only if tiled annotations are off + comphelper::LibreOfficeKit::setTiledAnnotations(false); + LibLODocument_Impl* pDocument = loadDoc("BlankDrawDocument.odg"); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + ViewCallback aView1(pDocument); + + // Add a new comment + OString aCommandArgs; + { + tools::JsonWriter aJson; + addParameter(aJson, "Text", "string", "Comment"); + addParameter(aJson, "Author", "string", "LOK User1"); + aCommandArgs = aJson.finishAndGetAsOString(); + } + + pDocument->pClass->postUnoCommand(pDocument, ".uno:InsertAnnotation", aCommandArgs.getStr(), false); + Scheduler::ProcessEventsToIdle(); + + // We received a LOK_CALLBACK_COMMENT callback with comment 'Add' action + CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView1.m_aCommentCallbackResult.get<std::string>("action")); + int nCommentId1 = aView1.m_aCommentCallbackResult.get<int>("id"); + + // Edit the previously added comment + { + tools::JsonWriter aJson; + addParameter(aJson, "Id", "string", OString::number(nCommentId1)); + addParameter(aJson, "Text", "string", "Edited comment"); + aCommandArgs = aJson.finishAndGetAsOString(); + } + + pDocument->pClass->postUnoCommand(pDocument, ".uno:EditAnnotation", aCommandArgs.getStr(), false); + Scheduler::ProcessEventsToIdle(); + + // We received a LOK_CALLBACK_COMMENT callback with comment 'Modify' action + CPPUNIT_ASSERT_EQUAL(std::string("Modify"), aView1.m_aCommentCallbackResult.get<std::string>("action")); + CPPUNIT_ASSERT_EQUAL(nCommentId1, aView1.m_aCommentCallbackResult.get<int>("id")); + + // Delete Comment + { + tools::JsonWriter aJson; + addParameter(aJson, "Id", "string", OString::number(nCommentId1)); + aCommandArgs = aJson.finishAndGetAsOString(); + } + pDocument->pClass->postUnoCommand(pDocument, ".uno:DeleteAnnotation", aCommandArgs.getStr(), false); + Scheduler::ProcessEventsToIdle(); + + // We received a LOK_CALLBACK_COMMENT callback with comment 'Remove' action + CPPUNIT_ASSERT_EQUAL(std::string("Remove"), aView1.m_aCommentCallbackResult.get<std::string>("action")); + CPPUNIT_ASSERT_EQUAL(nCommentId1, aView1.m_aCommentCallbackResult.get<int>("id")); +} + +void DesktopLOKTest::testRunMacro() +{ + LibLibreOffice_Impl aOffice; + bool bGoodMacro, bNonExistentMacro; + + // Tools macros come pre-installed in system share/basic folder, + bGoodMacro = aOffice.m_pOfficeClass->runMacro(&aOffice, "macro:///Tools.Debug.ActivateReadOnlyFlag()"); + CPPUNIT_ASSERT(bGoodMacro); + + bNonExistentMacro = aOffice.m_pOfficeClass->runMacro(&aOffice, "macro:///I.Am.Not(There)"); + CPPUNIT_ASSERT(!bNonExistentMacro); +} + +void DesktopLOKTest::testExtractParameter() +{ + OUString aOptions("Language=de-DE"); + OUString aValue = extractParameter(aOptions, u"Language"); + CPPUNIT_ASSERT_EQUAL(OUString("de-DE"), aValue); + CPPUNIT_ASSERT_EQUAL(OUString(), aOptions); + + aOptions = "Language=en-US,Something"; + aValue = extractParameter(aOptions, u"Language"); + CPPUNIT_ASSERT_EQUAL(OUString("en-US"), aValue); + CPPUNIT_ASSERT_EQUAL(OUString("Something"), aOptions); + + aOptions = "SomethingElse,Language=cs-CZ"; + aValue = extractParameter(aOptions, u"Language"); + CPPUNIT_ASSERT_EQUAL(OUString("cs-CZ"), aValue); + CPPUNIT_ASSERT_EQUAL(OUString("SomethingElse"), aOptions); + + aOptions = "Something1,Language=hu-HU,Something2"; + aValue = extractParameter(aOptions, u"Language"); + CPPUNIT_ASSERT_EQUAL(OUString("hu-HU"), aValue); + CPPUNIT_ASSERT_EQUAL(OUString("Something1,Something2"), aOptions); + + aOptions = "Something1,Something2=blah,Something3"; + aValue = extractParameter(aOptions, u"Language"); + CPPUNIT_ASSERT_EQUAL(OUString(), aValue); + CPPUNIT_ASSERT_EQUAL(OUString("Something1,Something2=blah,Something3"), aOptions); +} + +void DesktopLOKTest::readFileIntoByteVector(std::u16string_view sFilename, std::vector<unsigned char> & rByteVector) +{ + rByteVector.clear(); + OUString aURL = createFileURL(sFilename); + SvFileStream aStream(aURL, StreamMode::READ); + rByteVector.resize(aStream.remainingSize()); + aStream.ReadBytes(rByteVector.data(), aStream.remainingSize()); +} + +void DesktopLOKTest::testGetSignatureState_Signed() +{ + LibLODocument_Impl* pDocument = loadDoc("signed.odt"); + Scheduler::ProcessEventsToIdle(); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + int nState = pDocument->m_pDocumentClass->getSignatureState(pDocument); + if (nState == 1) + { + // Already SignatureState::OK, then can't test the effect of trusting new CAs. + return; + } + + CPPUNIT_ASSERT_EQUAL(int(4), nState); + + std::vector<unsigned char> aCertificate; + { + readFileIntoByteVector(u"rootCA.der", aCertificate); + bool bResult = pDocument->m_pDocumentClass->addCertificate( + pDocument, aCertificate.data(), int(aCertificate.size())); + CPPUNIT_ASSERT(bResult); + } + + { + readFileIntoByteVector(u"intermediateRootCA.der", aCertificate); + bool bResult = pDocument->m_pDocumentClass->addCertificate( + pDocument, aCertificate.data(), int(aCertificate.size())); + CPPUNIT_ASSERT(bResult); + } + + nState = pDocument->m_pDocumentClass->getSignatureState(pDocument); + CPPUNIT_ASSERT_EQUAL(int(1), nState); +} + +void DesktopLOKTest::testGetSignatureState_NonSigned() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + Scheduler::ProcessEventsToIdle(); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + int nState = pDocument->m_pDocumentClass->getSignatureState(pDocument); + CPPUNIT_ASSERT_EQUAL(int(0), nState); +} + +#if 0 // broken with system nss on RHEL 7 +void DesktopLOKTest::testInsertCertificate_DER_ODT() +{ + // Load the document, save it into a temp file and load that file again + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, maTempFile.GetURL().toUtf8().getStr(), "odt", nullptr)); + closeDoc(); + + pDocument = loadDocUrl(maTempFile.GetURL(), LOK_DOCTYPE_TEXT); + + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT(mxComponent.is()); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + Scheduler::ProcessEventsToIdle(); + + std::vector<unsigned char> aCertificate; + std::vector<unsigned char> aPrivateKey; + + { + readFileIntoByteVector(u"rootCA.der", aCertificate); + + bool bResult = pDocument->m_pDocumentClass->addCertificate( + pDocument, aCertificate.data(), int(aCertificate.size())); + CPPUNIT_ASSERT(bResult); + } + + { + readFileIntoByteVector(u"intermediateRootCA.der", aCertificate); + + bool bResult = pDocument->m_pDocumentClass->addCertificate( + pDocument, aCertificate.data(), int(aCertificate.size())); + CPPUNIT_ASSERT(bResult); + } + + { + readFileIntoByteVector(u"certificate.der", aCertificate); + readFileIntoByteVector(u"certificatePrivateKey.der", aPrivateKey); + + bool bResult = pDocument->m_pDocumentClass->insertCertificate(pDocument, + aCertificate.data(), int(aCertificate.size()), + aPrivateKey.data(), int(aPrivateKey.size())); + CPPUNIT_ASSERT(bResult); + } + + int nState = pDocument->m_pDocumentClass->getSignatureState(pDocument); + CPPUNIT_ASSERT_EQUAL(int(1), nState); +} + + +void DesktopLOKTest::testInsertCertificate_PEM_ODT() +{ + // Load the document, save it into a temp file and load that file again + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, maTempFile.GetURL().toUtf8().getStr(), "odt", nullptr)); + closeDoc(); + + pDocument = loadDocUrl(maTempFile.GetURL(), LOK_DOCTYPE_TEXT); + + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT(mxComponent.is()); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + Scheduler::ProcessEventsToIdle(); + + std::vector<unsigned char> aCertificate; + std::vector<unsigned char> aPrivateKey; + + { + readFileIntoByteVector(u"test-cert-chain-1.pem", aCertificate); + + bool bResult = pDocument->m_pDocumentClass->addCertificate( + pDocument, aCertificate.data(), int(aCertificate.size())); + CPPUNIT_ASSERT(bResult); + } + + { + readFileIntoByteVector(u"test-cert-chain-2.pem", aCertificate); + + bool bResult = pDocument->m_pDocumentClass->addCertificate( + pDocument, aCertificate.data(), int(aCertificate.size())); + CPPUNIT_ASSERT(bResult); + } + + { + readFileIntoByteVector(u"test-cert-chain-3.pem", aCertificate); + + bool bResult = pDocument->m_pDocumentClass->addCertificate( + pDocument, aCertificate.data(), int(aCertificate.size())); + CPPUNIT_ASSERT(bResult); + } + + { + readFileIntoByteVector(u"test-cert-signing.pem", aCertificate); + readFileIntoByteVector(u"test-PK-signing.pem", aPrivateKey); + + bool bResult = pDocument->m_pDocumentClass->insertCertificate(pDocument, + aCertificate.data(), int(aCertificate.size()), + aPrivateKey.data(), int(aPrivateKey.size())); + CPPUNIT_ASSERT(bResult); + } + + int nState = pDocument->m_pDocumentClass->getSignatureState(pDocument); + CPPUNIT_ASSERT_EQUAL(int(1), nState); +} + +void DesktopLOKTest::testInsertCertificate_PEM_DOCX() +{ + // Load the document, save it into a temp file and load that file again + LibLODocument_Impl* pDocument = loadDoc("blank_text.docx"); + CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, maTempFile.GetURL().toUtf8().getStr(), "docx", nullptr)); + closeDoc(); + + pDocument = loadDocUrl(maTempFile.GetURL(), LOK_DOCTYPE_TEXT); + + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT(mxComponent.is()); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + Scheduler::ProcessEventsToIdle(); + + std::vector<unsigned char> aCertificate; + std::vector<unsigned char> aPrivateKey; + + { + readFileIntoByteVector(u"test-cert-chain-1.pem", aCertificate); + + bool bResult = pDocument->m_pDocumentClass->addCertificate( + pDocument, aCertificate.data(), int(aCertificate.size())); + CPPUNIT_ASSERT(bResult); + } + + { + readFileIntoByteVector(u"test-cert-chain-2.pem", aCertificate); + + bool bResult = pDocument->m_pDocumentClass->addCertificate( + pDocument, aCertificate.data(), int(aCertificate.size())); + CPPUNIT_ASSERT(bResult); + } + + { + readFileIntoByteVector(u"test-cert-chain-3.pem", aCertificate); + + bool bResult = pDocument->m_pDocumentClass->addCertificate( + pDocument, aCertificate.data(), int(aCertificate.size())); + CPPUNIT_ASSERT(bResult); + } + + { + readFileIntoByteVector(u"test-cert-signing.pem", aCertificate); + readFileIntoByteVector(u"test-PK-signing.pem", aPrivateKey); + + bool bResult = pDocument->m_pDocumentClass->insertCertificate(pDocument, + aCertificate.data(), int(aCertificate.size()), + aPrivateKey.data(), int(aPrivateKey.size())); + CPPUNIT_ASSERT(bResult); + } + + int nState = pDocument->m_pDocumentClass->getSignatureState(pDocument); + CPPUNIT_ASSERT_EQUAL(int(5), nState); +} +#endif + +void DesktopLOKTest::testSignDocument_PEM_PDF() +{ + // Load the document, save it into a temp file and load that file again + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT(mxComponent.is()); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + Scheduler::ProcessEventsToIdle(); + + std::vector<unsigned char> aCertificate; + std::vector<unsigned char> aPrivateKey; + + { + readFileIntoByteVector(u"test-cert-chain-1.pem", aCertificate); + + bool bResult = pDocument->m_pDocumentClass->addCertificate( + pDocument, aCertificate.data(), int(aCertificate.size())); + CPPUNIT_ASSERT(bResult); + } + + { + readFileIntoByteVector(u"test-cert-chain-2.pem", aCertificate); + + bool bResult = pDocument->m_pDocumentClass->addCertificate( + pDocument, aCertificate.data(), int(aCertificate.size())); + CPPUNIT_ASSERT(bResult); + } + + { + readFileIntoByteVector(u"test-cert-chain-3.pem", aCertificate); + + bool bResult = pDocument->m_pDocumentClass->addCertificate( + pDocument, aCertificate.data(), int(aCertificate.size())); + CPPUNIT_ASSERT(bResult); + } + + CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, maTempFile.GetURL().toUtf8().getStr(), "pdf", nullptr)); + + closeDoc(); + + Scheduler::ProcessEventsToIdle(); + + readFileIntoByteVector(u"test-cert-signing.pem", aCertificate); + readFileIntoByteVector(u"test-PK-signing.pem", aPrivateKey); + + LibLibreOffice_Impl aOffice; + bool bResult = aOffice.m_pOfficeClass->signDocument(&aOffice, maTempFile.GetURL().toUtf8().getStr(), + aCertificate.data(), int(aCertificate.size()), + aPrivateKey.data(), int(aPrivateKey.size())); + + CPPUNIT_ASSERT(bResult); +} + +void DesktopLOKTest::testTextSelectionHandles() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); + + OString aText("hello"_ostr); + CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "text/plain;charset=utf-8", aText.getStr(), aText.getLength())); + + // select the inserted text + pDocument->pClass->postUnoCommand(pDocument, ".uno:SelectAll", nullptr, false); + Scheduler::ProcessEventsToIdle(); + char* pText = pDocument->pClass->getTextSelection(pDocument, "text/plain;charset=utf-8", nullptr); + CPPUNIT_ASSERT_EQUAL(aText, OString(pText)); + free(pText); + CPPUNIT_ASSERT_EQUAL("1418, 1418, 0, 275"_ostr, m_aTextSelectionStart); + CPPUNIT_ASSERT_EQUAL("1898, 1418, 0, 275"_ostr, m_aTextSelectionEnd); + + // deselect & check + m_aTextSelectionStart = ""_ostr; + m_aTextSelectionEnd = ""_ostr; + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, com::sun::star::awt::Key::ESCAPE); + Scheduler::ProcessEventsToIdle(); + pText = pDocument->pClass->getTextSelection(pDocument, "text/plain;charset=utf-8", nullptr); + CPPUNIT_ASSERT_EQUAL(static_cast<char *>(nullptr), pText); + free(pText); + CPPUNIT_ASSERT_EQUAL(OString(), m_aTextSelectionStart); + CPPUNIT_ASSERT_EQUAL(OString(), m_aTextSelectionEnd); + + // select again; the positions of the selection handles have to be sent + // again + pDocument->pClass->postUnoCommand(pDocument, ".uno:SelectAll", nullptr, false); + Scheduler::ProcessEventsToIdle(); + pText = pDocument->pClass->getTextSelection(pDocument, "text/plain;charset=utf-8", nullptr); + CPPUNIT_ASSERT_EQUAL(aText, OString(pText)); + free(pText); + CPPUNIT_ASSERT_EQUAL("1418, 1418, 0, 275"_ostr, m_aTextSelectionStart); + CPPUNIT_ASSERT_EQUAL("1898, 1418, 0, 275"_ostr, m_aTextSelectionEnd); +} + +void DesktopLOKTest::testDialogPaste() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + pDocument->pClass->postUnoCommand(pDocument, ".uno:HyperlinkDialog", nullptr, false); + Scheduler::ProcessEventsToIdle(); + + SfxViewShell* pViewShell = SfxViewShell::Current(); + pViewShell->GetViewFrame().GetBindings().Update(); + + VclPtr<vcl::Window> pWindow(Application::GetActiveTopWindow()); + CPPUNIT_ASSERT(pWindow); + + pDocument->pClass->postWindow(pDocument, pWindow->GetLOKWindowId(), LOK_WINDOW_PASTE, + "{ \"MimeType\" : { \"type\" : \"string\", \"value\" : \"text/plain;charset=utf-8\" }, \"Data\" : { \"type\" : \"[]byte\", \"value\" : \"www.softwarelibre.org.bo\" } }"); + Scheduler::ProcessEventsToIdle(); + + Control* pCtrlFocused = GetFocusControl(pWindow.get()); + CPPUNIT_ASSERT(pCtrlFocused); + CPPUNIT_ASSERT_EQUAL(WindowType::COMBOBOX, pCtrlFocused->GetType()); + CPPUNIT_ASSERT_EQUAL(OUString("www.softwarelibre.org.bo"), pCtrlFocused->GetText()); + + static_cast<SystemWindow*>(pWindow.get())->Close(); + Scheduler::ProcessEventsToIdle(); +} + +void DesktopLOKTest::testComplexSelection() +{ + // Start with a blank text file and add contents. + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + static constexpr OString aText("hello world"_ostr); + + // Certainly not complex. + CPPUNIT_ASSERT_EQUAL(static_cast<int>(LOK_SELTYPE_NONE), pDocument->pClass->getSelectionType(pDocument)); + CPPUNIT_ASSERT_EQUAL(static_cast<int>(LOK_SELTYPE_NONE), pDocument->pClass->getSelectionTypeAndText(pDocument, + "", nullptr, nullptr)); + + // Paste text. + CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "text/plain;charset=utf-8", aText.getStr(), aText.getLength())); + + // No selection. + CPPUNIT_ASSERT_EQUAL(static_cast<int>(LOK_SELTYPE_NONE), pDocument->pClass->getSelectionType(pDocument)); + CPPUNIT_ASSERT_EQUAL(static_cast<int>(LOK_SELTYPE_NONE), pDocument->pClass->getSelectionTypeAndText(pDocument, + "", nullptr, nullptr)); + + // Paste an image. + OUString aFileURL = createFileURL(u"paste.jpg"); + std::ifstream aImageStream(aFileURL.toUtf8().copy(strlen("file://")).getStr()); + std::vector<char> aImageContents((std::istreambuf_iterator<char>(aImageStream)), std::istreambuf_iterator<char>()); + CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "image/jpeg", aImageContents.data(), aImageContents.size())); + + // Now select-all. + pDocument->pClass->postUnoCommand(pDocument, ".uno:SelectAll", nullptr, false); + Scheduler::ProcessEventsToIdle(); + + // Export as plain text, we should get only the text part "hello". + char* pText = pDocument->pClass->getTextSelection(pDocument, "text/plain;charset=utf-8", nullptr); + CPPUNIT_ASSERT(pText != nullptr); + CPPUNIT_ASSERT_EQUAL(aText, OString(pText)); + free(pText); + + // Export as rtf, we should also get the image. + pText = pDocument->pClass->getTextSelection(pDocument, "text/rtf", nullptr); + CPPUNIT_ASSERT(pText != nullptr); + CPPUNIT_ASSERT(std::string(pText).find(aText.getStr()) != std::string::npos); // Must have the text. + CPPUNIT_ASSERT(std::string(pText).find("pict{") != std::string::npos); // Must have the image as well. + free(pText); + + // Export as html, we should also get the image. + pText = pDocument->pClass->getTextSelection(pDocument, "text/html", nullptr); + CPPUNIT_ASSERT(pText != nullptr); + CPPUNIT_ASSERT(std::string(pText).find(aText.getStr()) != std::string::npos); // Must have the text. + CPPUNIT_ASSERT(std::string(pText).find("<img") != std::string::npos); // Must have the image as well. + free(pText); + + // We expect this to be complex. + CPPUNIT_ASSERT_EQUAL(static_cast<int>(LOK_SELTYPE_COMPLEX), pDocument->pClass->getSelectionType(pDocument)); + CPPUNIT_ASSERT_EQUAL(static_cast<int>(LOK_SELTYPE_COMPLEX), pDocument->pClass->getSelectionTypeAndText(pDocument, + "", nullptr, nullptr)); +} + +void DesktopLOKTest::testCalcSaveAs() +{ + LibLODocument_Impl* pDocument = loadDoc("sheets.ods"); + CPPUNIT_ASSERT(pDocument); + + // Enter some text, but don't commit. + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'X', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 'X', 0); + Scheduler::ProcessEventsToIdle(); + + // Save as a new file. + pDocument->pClass->saveAs(pDocument, maTempFile.GetURL().toUtf8().getStr(), "ods", nullptr); + closeDoc(); + + // Load the new document and verify that the in-flight changes are saved. + pDocument = loadDocUrl(maTempFile.GetURL(), LOK_DOCTYPE_SPREADSHEET); + CPPUNIT_ASSERT(pDocument); + + ViewCallback aView(pDocument); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + pDocument->m_pDocumentClass->registerCallback(pDocument, &ViewCallback::callback, &aView); + + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, KEY_RIGHT); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 0, KEY_RIGHT); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, KEY_LEFT); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 0, KEY_LEFT); + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL("X"_ostr, aView.m_aCellFormula); +} + +void DesktopLOKTest::testSpellcheckerMultiView() +{ + static constexpr OUString aLangISO(u"en-US"_ustr); + SvtSysLocaleOptions aSysLocaleOptions; + aSysLocaleOptions.SetLocaleConfigString(aLangISO); + aSysLocaleOptions.SetUILocaleConfigString(aLangISO); + comphelper::LibreOfficeKit::setLanguageTag(LanguageTag(aLangISO, true)); + + auto aSavedSettings = Application::GetSettings(); + std::unique_ptr<Resetter> pResetter( + new Resetter([&]() { Application::SetSettings(aSavedSettings); })); + AllSettings aSettings(aSavedSettings); + aSettings.SetLanguageTag(aLangISO, true); + Application::SetSettings(aSettings); + + LibLODocument_Impl* pDocument = loadDoc("sheet_with_image.ods", LOK_DOCTYPE_SPREADSHEET); + pDocument->pClass->setViewLanguage(pDocument, 0, "en-US"); // For spellchecking. + pDocument->pClass->initializeForRendering(pDocument, nullptr); + pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); + + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'a', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'a', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'a', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, com::sun::star::awt::Key::ESCAPE); + + // Start spellchecking. + pDocument->pClass->postUnoCommand(pDocument, ".uno:SpellDialog", nullptr, false); + + // Uncommenting this will result in a deadlock. + // Because the language configuration above is not effective, and no + // language is actually set, the spell-dialog finds no misspelled + // words, and displays a message box, which must be dismissed to + // continue. + // Need to fix the language configuration issue to enable this. + // Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(1, pDocument->m_pDocumentClass->getViewsCount(pDocument)); + + // Now create another view. + const int nViewId = pDocument->m_pDocumentClass->createView(pDocument); + CPPUNIT_ASSERT_EQUAL(2, pDocument->m_pDocumentClass->getViewsCount(pDocument)); + + // And destroy it. + pDocument->m_pDocumentClass->destroyView(pDocument, nViewId); + + // We should survive the destroyed view. + CPPUNIT_ASSERT_EQUAL(1, pDocument->m_pDocumentClass->getViewsCount(pDocument)); +} + +void DesktopLOKTest::testMultiDocuments() +{ + for (int i = 0; i < 3; i++) + { + // Load a document. + std::unique_ptr<LibLODocument_Impl> document1 = loadDocImpl("blank_text.odt"); + LibLODocument_Impl* pDocument1 = document1.get(); + CPPUNIT_ASSERT_EQUAL(1, pDocument1->m_pDocumentClass->getViewsCount(pDocument1)); + const int nDocId1 = pDocument1->mnDocumentId; + + const int nDoc1View0 = pDocument1->m_pDocumentClass->getView(pDocument1); + CPPUNIT_ASSERT_EQUAL(nDocId1, SfxLokHelper::getDocumentIdOfView(nDoc1View0)); + const int nDoc1View1 = pDocument1->m_pDocumentClass->createView(pDocument1); + CPPUNIT_ASSERT_EQUAL(nDoc1View1, pDocument1->m_pDocumentClass->getView(pDocument1)); + CPPUNIT_ASSERT_EQUAL(nDocId1, SfxLokHelper::getDocumentIdOfView(nDoc1View1)); + CPPUNIT_ASSERT_EQUAL(2, pDocument1->m_pDocumentClass->getViewsCount(pDocument1)); + + // Validate the views of document 1. + std::vector<int> aViewIdsDoc1(2); + CPPUNIT_ASSERT(pDocument1->m_pDocumentClass->getViewIds(pDocument1, aViewIdsDoc1.data(), aViewIdsDoc1.size())); + CPPUNIT_ASSERT_EQUAL(nDoc1View0, aViewIdsDoc1[0]); + CPPUNIT_ASSERT_EQUAL(nDoc1View1, aViewIdsDoc1[1]); + + CPPUNIT_ASSERT_EQUAL(nDoc1View1, pDocument1->m_pDocumentClass->getView(pDocument1)); + CPPUNIT_ASSERT_EQUAL(nDocId1, SfxLokHelper::getDocumentIdOfView(nDoc1View1)); + pDocument1->m_pDocumentClass->setView(pDocument1, nDoc1View0); + CPPUNIT_ASSERT_EQUAL(nDoc1View0, pDocument1->m_pDocumentClass->getView(pDocument1)); + CPPUNIT_ASSERT_EQUAL(nDocId1, SfxLokHelper::getDocumentIdOfView(nDoc1View0)); + pDocument1->m_pDocumentClass->setView(pDocument1, nDoc1View1); + CPPUNIT_ASSERT_EQUAL(nDoc1View1, pDocument1->m_pDocumentClass->getView(pDocument1)); + CPPUNIT_ASSERT_EQUAL(nDocId1, SfxLokHelper::getDocumentIdOfView(nDoc1View1)); + CPPUNIT_ASSERT_EQUAL(2, pDocument1->m_pDocumentClass->getViewsCount(pDocument1)); + + // Load another document. + std::unique_ptr<LibLODocument_Impl> document2 = loadDocImpl("blank_presentation.odp"); + LibLODocument_Impl* pDocument2 = document2.get(); + CPPUNIT_ASSERT_EQUAL(1, pDocument2->m_pDocumentClass->getViewsCount(pDocument2)); + const int nDocId2 = pDocument2->mnDocumentId; + + const int nDoc2View0 = pDocument2->m_pDocumentClass->getView(pDocument2); + CPPUNIT_ASSERT_EQUAL(nDocId2, SfxLokHelper::getDocumentIdOfView(nDoc2View0)); + const int nDoc2View1 = pDocument2->m_pDocumentClass->createView(pDocument2); + CPPUNIT_ASSERT_EQUAL(nDoc2View1, pDocument2->m_pDocumentClass->getView(pDocument2)); + CPPUNIT_ASSERT_EQUAL(nDocId2, SfxLokHelper::getDocumentIdOfView(nDoc2View1)); + CPPUNIT_ASSERT_EQUAL(2, pDocument2->m_pDocumentClass->getViewsCount(pDocument2)); + + // Validate the views of document 2. + std::vector<int> aViewIdsDoc2(2); + CPPUNIT_ASSERT(pDocument2->m_pDocumentClass->getViewIds(pDocument2, aViewIdsDoc2.data(), aViewIdsDoc2.size())); + CPPUNIT_ASSERT_EQUAL(nDoc2View0, aViewIdsDoc2[0]); + CPPUNIT_ASSERT_EQUAL(nDoc2View1, aViewIdsDoc2[1]); + + CPPUNIT_ASSERT_EQUAL(nDoc2View1, pDocument2->m_pDocumentClass->getView(pDocument2)); + CPPUNIT_ASSERT_EQUAL(nDocId2, SfxLokHelper::getDocumentIdOfView(nDoc2View1)); + pDocument2->m_pDocumentClass->setView(pDocument2, nDoc2View0); + CPPUNIT_ASSERT_EQUAL(nDoc2View0, pDocument2->m_pDocumentClass->getView(pDocument2)); + CPPUNIT_ASSERT_EQUAL(nDocId2, SfxLokHelper::getDocumentIdOfView(nDoc2View0)); + pDocument2->m_pDocumentClass->setView(pDocument2, nDoc2View1); + CPPUNIT_ASSERT_EQUAL(nDoc2View1, pDocument2->m_pDocumentClass->getView(pDocument2)); + CPPUNIT_ASSERT_EQUAL(nDocId2, SfxLokHelper::getDocumentIdOfView(nDoc2View1)); + CPPUNIT_ASSERT_EQUAL(2, pDocument2->m_pDocumentClass->getViewsCount(pDocument2)); + + // The views of document1 should be unchanged. + CPPUNIT_ASSERT(pDocument1->m_pDocumentClass->getViewIds(pDocument1, aViewIdsDoc1.data(), aViewIdsDoc1.size())); + CPPUNIT_ASSERT_EQUAL(nDoc1View0, aViewIdsDoc1[0]); + CPPUNIT_ASSERT_EQUAL(nDoc1View1, aViewIdsDoc1[1]); + // Switch views in the first doc. + CPPUNIT_ASSERT_EQUAL(nDocId1, SfxLokHelper::getDocumentIdOfView(nDoc1View0)); + pDocument1->m_pDocumentClass->setView(pDocument1, nDoc1View0); + CPPUNIT_ASSERT_EQUAL(nDoc1View0, pDocument1->m_pDocumentClass->getView(pDocument1)); + CPPUNIT_ASSERT_EQUAL(nDocId1, SfxLokHelper::getDocumentIdOfView(nDoc1View1)); + pDocument1->m_pDocumentClass->destroyView(pDocument1, nDoc1View1); + CPPUNIT_ASSERT_EQUAL(1, pDocument1->m_pDocumentClass->getViewsCount(pDocument1)); + + // The views of document2 should be unchanged. + CPPUNIT_ASSERT(pDocument2->m_pDocumentClass->getViewIds(pDocument2, aViewIdsDoc2.data(), aViewIdsDoc2.size())); + CPPUNIT_ASSERT_EQUAL(nDoc2View0, aViewIdsDoc2[0]); + CPPUNIT_ASSERT_EQUAL(nDoc2View1, aViewIdsDoc2[1]); + // Switch views in the second doc. + CPPUNIT_ASSERT_EQUAL(nDocId2, SfxLokHelper::getDocumentIdOfView(nDoc2View0)); + pDocument2->m_pDocumentClass->setView(pDocument2, nDoc2View0); + CPPUNIT_ASSERT_EQUAL(nDoc2View0, pDocument2->m_pDocumentClass->getView(pDocument2)); + CPPUNIT_ASSERT_EQUAL(nDocId2, SfxLokHelper::getDocumentIdOfView(nDoc2View1)); + pDocument2->m_pDocumentClass->destroyView(pDocument2, nDoc2View1); + CPPUNIT_ASSERT_EQUAL(1, pDocument2->m_pDocumentClass->getViewsCount(pDocument2)); + + closeDoc(document2); + + closeDoc(document1); + } +} + +namespace +{ + SfxChildWindow* lcl_initializeSidebar() + { + // in init.cxx we do setupSidebar which creates the controller, do it here + + SfxViewShell* pViewShell = SfxViewShell::Current(); + CPPUNIT_ASSERT(pViewShell); + + SfxViewFrame& rViewFrame = pViewShell->GetViewFrame(); + SfxChildWindow* pSideBar = rViewFrame.GetChildWindow(SID_SIDEBAR); + CPPUNIT_ASSERT(pSideBar); + + auto pDockingWin = dynamic_cast<sfx2::sidebar::SidebarDockingWindow *>(pSideBar->GetWindow()); + CPPUNIT_ASSERT(pDockingWin); + + pDockingWin->GetOrCreateSidebarController(); // just to create the controller + + return pSideBar; + } +}; + +void DesktopLOKTest::testControlState() +{ + LibLODocument_Impl* pDocument = loadDoc("search.ods"); + pDocument->pClass->postUnoCommand(pDocument, ".uno:StarShapes", nullptr, false); + lcl_initializeSidebar(); + Scheduler::ProcessEventsToIdle(); + + boost::property_tree::ptree aState; + SfxViewShell* pViewShell = SfxViewShell::Current(); + pViewShell->GetViewFrame().GetBindings().Update(); + pViewShell->GetViewFrame().GetBindings().QueryControlState(SID_ATTR_TRANSFORM_WIDTH, aState); + CPPUNIT_ASSERT(!aState.empty()); +} + +void DesktopLOKTest::testMetricField() +{ + LibLODocument_Impl* pDocument = loadDoc("search.ods"); + pDocument->pClass->postUnoCommand(pDocument, ".uno:StarShapes", nullptr, false); + SfxChildWindow* pSideBar = lcl_initializeSidebar(); + Scheduler::ProcessEventsToIdle(); + + vcl::Window* pWin = pSideBar->GetWindow(); + CPPUNIT_ASSERT(pWin); + + WindowUIObject aWinUI(pWin); + std::unique_ptr<UIObject> pUIWin(aWinUI.get_child("selectwidth")); + CPPUNIT_ASSERT(pUIWin); + + StringMap aMap; + aMap["VALUE"] = "75.06"; + pUIWin->execute("VALUE", aMap); + + StringMap aRet = pUIWin->get_state(); + CPPUNIT_ASSERT_EQUAL(aMap["VALUE"], aRet["Value"]); +} + +void DesktopLOKTest::testJumpCursor() +{ + comphelper::LibreOfficeKit::setTiledAnnotations(false); + + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'B', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'o', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'l', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'i', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'v', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'i', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'a', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, com::sun::star::awt::Key::ESCAPE); + Scheduler::ProcessEventsToIdle(); + + // There is a cursor jump to (0, 0) due to + // mpOutlinerView->SetOutputArea( PixelToLogic( tools::Rectangle(0,0,1,1) ) ); + // when creating a comment + ViewCallback aView1(pDocument); + + pDocument->pClass->postUnoCommand(pDocument, ".uno:InsertAnnotation", nullptr, true); + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT(!aView1.m_bZeroCursor); + + comphelper::LibreOfficeKit::setTiledAnnotations(true); +} + +void DesktopLOKTest::testRenderSearchResult_WriterNode() +{ + constexpr const bool bDumpBitmap = false; + + LibLODocument_Impl* pDocument = loadDoc("SearchIndexResultTest.odt"); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + + Scheduler::ProcessEventsToIdle(); + + unsigned char* pBuffer = nullptr; + OString aPayload = + "<indexing>" + "<paragraph node_type=\"writer\" index=\"19\">ABC</paragraph>" + "</indexing>"_ostr; + + int nWidth = 0; + int nHeight = 0; + size_t nByteSize = 0; + + bool bResult = pDocument->m_pDocumentClass->renderSearchResult(pDocument, aPayload.getStr(), &pBuffer, &nWidth, &nHeight, &nByteSize); + + CPPUNIT_ASSERT(bResult); + CPPUNIT_ASSERT(pBuffer); + + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(642, nWidth); + CPPUNIT_ASSERT_EQUAL(561, nHeight); + CPPUNIT_ASSERT_EQUAL(size_t(1440648), nByteSize); + + const sal_uInt8* pD = reinterpret_cast<const sal_uInt8*>(pBuffer); + BitmapEx aBitmap = vcl::bitmap::CreateFromData(pD, nWidth, nHeight, nWidth * 4, /*nBitsPerPixel*/32, true, true); + + if (bDumpBitmap) + { + SvFileStream aStream("~/SearchResultBitmap.png", StreamMode::WRITE | StreamMode::TRUNC); + vcl::PngImageWriter aPNGWriter(aStream); + aPNGWriter.write(aBitmap); + } + CPPUNIT_ASSERT_EQUAL(tools::Long(642), aBitmap.GetSizePixel().Width()); + CPPUNIT_ASSERT_EQUAL(tools::Long(561), aBitmap.GetSizePixel().Height()); + + std::free(pBuffer); +} + +void DesktopLOKTest::testRenderSearchResult_CommonNode() +{ + constexpr const bool bDumpBitmap = false; + + LibLODocument_Impl* pDocument = loadDoc("SearchIndexResultShapeTest.odt"); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + + Scheduler::ProcessEventsToIdle(); + + unsigned char* pBuffer = nullptr; + OString aPayload = + "<indexing>" + "<paragraph node_type=\"common\" index=\"0\" object_name=\"Shape 1\" />" + "</indexing>"_ostr; + + int nWidth = 0; + int nHeight = 0; + size_t nByteSize = 0; + + bool bResult = pDocument->m_pDocumentClass->renderSearchResult(pDocument, aPayload.getStr(), &pBuffer, &nWidth, &nHeight, &nByteSize); + + CPPUNIT_ASSERT(bResult); + CPPUNIT_ASSERT(pBuffer); + + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(192, nWidth); + CPPUNIT_ASSERT_EQUAL(96, nHeight); + CPPUNIT_ASSERT_EQUAL(size_t(73728), nByteSize); + + const sal_uInt8* pD = reinterpret_cast<const sal_uInt8*>(pBuffer); + BitmapEx aBitmap = vcl::bitmap::CreateFromData(pD, nWidth, nHeight, nWidth * 4, /*nBitsPerPixel*/32, true, true); + + if (bDumpBitmap) + { + SvFileStream aStream("~/SearchResultBitmap.png", StreamMode::WRITE | StreamMode::TRUNC); + vcl::PngImageWriter aPNGWriter(aStream); + aPNGWriter.write(aBitmap); + } + CPPUNIT_ASSERT_EQUAL(tools::Long(192), aBitmap.GetSizePixel().Width()); + CPPUNIT_ASSERT_EQUAL(tools::Long(96), aBitmap.GetSizePixel().Height()); + + std::free(pBuffer); +} + +static void lcl_repeatKeyStroke(LibLODocument_Impl *pDocument, int nCharCode, int nKeyCode, size_t nCount) +{ + for (size_t nCtr = 0; nCtr < nCount; ++nCtr) + { + pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, nCharCode, nKeyCode); + pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, nCharCode, nKeyCode); + } +} + +void DesktopLOKTest::testNoDuplicateTableSelection() +{ + LibLODocument_Impl* pDocument = loadDoc("table-selection.odt"); + + // Create view 1. + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + ViewCallback aView1(pDocument); + + lcl_repeatKeyStroke(pDocument, 0, KEY_DOWN, 1); + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT_EQUAL(1, aView1.m_nTableSelectionCount); + CPPUNIT_ASSERT(aView1.m_bEmptyTableSelection); + + aView1.m_nTableSelectionCount = 0; + // Go to Table1. + lcl_repeatKeyStroke(pDocument, 0, KEY_DOWN, 1); + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT_EQUAL(1, aView1.m_nTableSelectionCount); + CPPUNIT_ASSERT(!aView1.m_bEmptyTableSelection); + + aView1.m_nTableSelectionCount = 0; + // Move to the last row in Table1. + lcl_repeatKeyStroke(pDocument, 0, KEY_DOWN, 2); + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT_EQUAL(0, aView1.m_nTableSelectionCount); + + // Go outside Table1. + lcl_repeatKeyStroke(pDocument, 0, KEY_DOWN, 1); + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT_EQUAL(1, aView1.m_nTableSelectionCount); + CPPUNIT_ASSERT(aView1.m_bEmptyTableSelection); +} + +void DesktopLOKTest::testMultiViewTableSelection() +{ + LibLODocument_Impl* pDocument = loadDoc("table-selection.odt"); + + // Create view 1. + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + ViewCallback aView1(pDocument); + int nView1 = pDocument->m_pDocumentClass->getView(pDocument); + + // Create view 2. + pDocument->m_pDocumentClass->createView(pDocument); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + ViewCallback aView2(pDocument); + int nView2 = pDocument->m_pDocumentClass->getView(pDocument); + + // switch to view 1. + pDocument->m_pDocumentClass->setView(pDocument, nView1); + lcl_repeatKeyStroke(pDocument, 0, KEY_DOWN, 1); + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT_EQUAL(1, aView1.m_nTableSelectionCount); + CPPUNIT_ASSERT_EQUAL(1, aView2.m_nTableSelectionCount); + CPPUNIT_ASSERT(aView1.m_bEmptyTableSelection); + CPPUNIT_ASSERT(aView2.m_bEmptyTableSelection); + + aView1.m_nTableSelectionCount = 0; + aView2.m_nTableSelectionCount = 0; + + pDocument->m_pDocumentClass->setView(pDocument, nView1); + // Go to Table1. + lcl_repeatKeyStroke(pDocument, 0, KEY_DOWN, 1); + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT_EQUAL(1, aView1.m_nTableSelectionCount); + CPPUNIT_ASSERT_EQUAL(0, aView2.m_nTableSelectionCount); + + aView1.m_nTableSelectionCount = 0; + // Switch to view 2 + pDocument->m_pDocumentClass->setView(pDocument, nView2); + // Go to Table2 in view 2. + lcl_repeatKeyStroke(pDocument, 0, KEY_DOWN, 7); + Scheduler::ProcessEventsToIdle(); + // View1 should not get any table selection messages. + CPPUNIT_ASSERT_EQUAL(0, aView1.m_nTableSelectionCount); + // View2 will first get table selection of Table1, then empty selection, and finally on 7th down arrow keypress, + // it will get table-selection of Table2. So in total it should get 3 table selections. + CPPUNIT_ASSERT_EQUAL(3, aView2.m_nTableSelectionCount); + CPPUNIT_ASSERT(!aView2.m_bEmptyTableSelection); + + aView1.m_nTableSelectionCount = 0; + aView2.m_nTableSelectionCount = 0; + + // Switch to view 1 + pDocument->m_pDocumentClass->setView(pDocument, nView1); + // Go out of Table1 and re-enter.. + lcl_repeatKeyStroke(pDocument, 0, KEY_UP, 1); + lcl_repeatKeyStroke(pDocument, 0, KEY_DOWN, 1); + Scheduler::ProcessEventsToIdle(); + // View1 should get one empty table selection, then get Table1 selection. + CPPUNIT_ASSERT_EQUAL(2, aView1.m_nTableSelectionCount); + // View2 should not get any table selection. + CPPUNIT_ASSERT_EQUAL(0, aView2.m_nTableSelectionCount); + CPPUNIT_ASSERT(!aView1.m_bEmptyTableSelection); +} + +void DesktopLOKTest::testColorPaletteCallback() +{ + LibLODocument_Impl* pDocument = loadDoc("ThemeDocument.docx"); + + // Create view 1. + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + ViewCallback aView1(pDocument); + Scheduler::ProcessEventsToIdle(); + { + CPPUNIT_ASSERT_EQUAL(1, aView1.m_nColorPaletteCallbackCount); + boost::property_tree::ptree aValues = aView1.m_aColorPaletteCallbackResult.get_child("ThemeColors"); + CPPUNIT_ASSERT(!aValues.empty()); + CPPUNIT_ASSERT_EQUAL(size_t(6), aValues.size()); + } + + // Create view 2. + pDocument->m_pDocumentClass->createView(pDocument); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + ViewCallback aView2(pDocument); + Scheduler::ProcessEventsToIdle(); + { + CPPUNIT_ASSERT_EQUAL(1, aView2.m_nColorPaletteCallbackCount); + boost::property_tree::ptree aValues = aView1.m_aColorPaletteCallbackResult.get_child("ThemeColors"); + CPPUNIT_ASSERT(!aValues.empty()); + CPPUNIT_ASSERT_EQUAL(size_t(6), aValues.size()); + } +} + +namespace { + +constexpr size_t classOffset(int i) +{ + return sizeof(static_cast<struct _LibreOfficeKitClass*>(nullptr)->nSize) + i * sizeof(void*); +} + +constexpr size_t documentClassOffset(int i) +{ + return sizeof(static_cast<struct _LibreOfficeKitDocumentClass*>(nullptr)->nSize) + i * sizeof(void*); +} + +} + +void DesktopLOKTest::testABI() +{ + // STABLE ABI, NEVER CHANGE (unless there's a very good reason, agreed by ESC, etc.) + CPPUNIT_ASSERT_EQUAL(classOffset(0), offsetof(struct _LibreOfficeKitClass, destroy)); + CPPUNIT_ASSERT_EQUAL(classOffset(1), offsetof(struct _LibreOfficeKitClass, documentLoad)); + CPPUNIT_ASSERT_EQUAL(classOffset(2), offsetof(struct _LibreOfficeKitClass, getError)); + CPPUNIT_ASSERT_EQUAL(classOffset(3), offsetof(struct _LibreOfficeKitClass, documentLoadWithOptions)); + CPPUNIT_ASSERT_EQUAL(classOffset(4), offsetof(struct _LibreOfficeKitClass, freeError)); + CPPUNIT_ASSERT_EQUAL(classOffset(5), offsetof(struct _LibreOfficeKitClass, registerCallback)); + CPPUNIT_ASSERT_EQUAL(classOffset(6), offsetof(struct _LibreOfficeKitClass, getFilterTypes)); + CPPUNIT_ASSERT_EQUAL(classOffset(7), offsetof(struct _LibreOfficeKitClass, setOptionalFeatures)); + CPPUNIT_ASSERT_EQUAL(classOffset(8), offsetof(struct _LibreOfficeKitClass, setDocumentPassword)); + CPPUNIT_ASSERT_EQUAL(classOffset(9), offsetof(struct _LibreOfficeKitClass, getVersionInfo)); + CPPUNIT_ASSERT_EQUAL(classOffset(10), offsetof(struct _LibreOfficeKitClass, runMacro)); + CPPUNIT_ASSERT_EQUAL(classOffset(11), offsetof(struct _LibreOfficeKitClass, signDocument)); + CPPUNIT_ASSERT_EQUAL(classOffset(12), offsetof(struct _LibreOfficeKitClass, runLoop)); + CPPUNIT_ASSERT_EQUAL(classOffset(13), offsetof(struct _LibreOfficeKitClass, sendDialogEvent)); + CPPUNIT_ASSERT_EQUAL(classOffset(14), offsetof(struct _LibreOfficeKitClass, setOption)); + CPPUNIT_ASSERT_EQUAL(classOffset(15), offsetof(struct _LibreOfficeKitClass, dumpState)); + CPPUNIT_ASSERT_EQUAL(classOffset(16), offsetof(struct _LibreOfficeKitClass, extractRequest)); + CPPUNIT_ASSERT_EQUAL(classOffset(17), offsetof(struct _LibreOfficeKitClass, trimMemory)); + CPPUNIT_ASSERT_EQUAL(classOffset(18), offsetof(struct _LibreOfficeKitClass, startURP)); + CPPUNIT_ASSERT_EQUAL(classOffset(19), offsetof(struct _LibreOfficeKitClass, stopURP)); + + // When extending LibreOfficeKit with a new function pointer, add new assert for the offsetof the + // new function pointer and bump this assert for the size of the class. + CPPUNIT_ASSERT_EQUAL(classOffset(20), sizeof(struct _LibreOfficeKitClass)); + + CPPUNIT_ASSERT_EQUAL(documentClassOffset(0), offsetof(struct _LibreOfficeKitDocumentClass, destroy)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(1), offsetof(struct _LibreOfficeKitDocumentClass, saveAs)); + + // Unstable ABI, but still think twice before changing this + // Eg. can't you add your new member at the end of the struct instead of + // in the middle? The thing you are changing - is it already part of some + // release? + CPPUNIT_ASSERT_EQUAL(documentClassOffset(2), offsetof(struct _LibreOfficeKitDocumentClass, getDocumentType)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(3), offsetof(struct _LibreOfficeKitDocumentClass, getParts)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(4), offsetof(struct _LibreOfficeKitDocumentClass, getPartPageRectangles)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(5), offsetof(struct _LibreOfficeKitDocumentClass, getPart)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(6), offsetof(struct _LibreOfficeKitDocumentClass, setPart)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(7), offsetof(struct _LibreOfficeKitDocumentClass, getPartName)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(8), offsetof(struct _LibreOfficeKitDocumentClass, setPartMode)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(9), offsetof(struct _LibreOfficeKitDocumentClass, paintTile)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(10), offsetof(struct _LibreOfficeKitDocumentClass, getTileMode)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(11), offsetof(struct _LibreOfficeKitDocumentClass, getDocumentSize)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(12), offsetof(struct _LibreOfficeKitDocumentClass, initializeForRendering)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(13), offsetof(struct _LibreOfficeKitDocumentClass, registerCallback)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(14), offsetof(struct _LibreOfficeKitDocumentClass, postKeyEvent)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(15), offsetof(struct _LibreOfficeKitDocumentClass, postMouseEvent)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(16), offsetof(struct _LibreOfficeKitDocumentClass, postUnoCommand)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(17), offsetof(struct _LibreOfficeKitDocumentClass, setTextSelection)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(18), offsetof(struct _LibreOfficeKitDocumentClass, getTextSelection)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(19), offsetof(struct _LibreOfficeKitDocumentClass, paste)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(20), offsetof(struct _LibreOfficeKitDocumentClass, setGraphicSelection)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(21), offsetof(struct _LibreOfficeKitDocumentClass, resetSelection)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(22), offsetof(struct _LibreOfficeKitDocumentClass, getCommandValues)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(23), offsetof(struct _LibreOfficeKitDocumentClass, setClientZoom)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(24), offsetof(struct _LibreOfficeKitDocumentClass, setClientVisibleArea)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(25), offsetof(struct _LibreOfficeKitDocumentClass, createView)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(26), offsetof(struct _LibreOfficeKitDocumentClass, destroyView)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(27), offsetof(struct _LibreOfficeKitDocumentClass, setView)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(28), offsetof(struct _LibreOfficeKitDocumentClass, getView)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(29), offsetof(struct _LibreOfficeKitDocumentClass, getViewsCount)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(30), offsetof(struct _LibreOfficeKitDocumentClass, renderFont)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(31), offsetof(struct _LibreOfficeKitDocumentClass, getPartHash)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(32), offsetof(struct _LibreOfficeKitDocumentClass, paintPartTile)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(33), offsetof(struct _LibreOfficeKitDocumentClass, getViewIds)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(34), offsetof(struct _LibreOfficeKitDocumentClass, setOutlineState)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(35), offsetof(struct _LibreOfficeKitDocumentClass, paintWindow)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(36), offsetof(struct _LibreOfficeKitDocumentClass, postWindow)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(37), offsetof(struct _LibreOfficeKitDocumentClass, postWindowKeyEvent)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(38), offsetof(struct _LibreOfficeKitDocumentClass, postWindowMouseEvent)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(39), offsetof(struct _LibreOfficeKitDocumentClass, setViewLanguage)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(40), offsetof(struct _LibreOfficeKitDocumentClass, postWindowExtTextInputEvent)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(41), offsetof(struct _LibreOfficeKitDocumentClass, getPartInfo)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(42), offsetof(struct _LibreOfficeKitDocumentClass, paintWindowDPI)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(43), offsetof(struct _LibreOfficeKitDocumentClass, insertCertificate)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(44), offsetof(struct _LibreOfficeKitDocumentClass, addCertificate)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(45), offsetof(struct _LibreOfficeKitDocumentClass, getSignatureState)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(46), offsetof(struct _LibreOfficeKitDocumentClass, renderShapeSelection)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(47), offsetof(struct _LibreOfficeKitDocumentClass, postWindowGestureEvent)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(48), offsetof(struct _LibreOfficeKitDocumentClass, createViewWithOptions)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(49), offsetof(struct _LibreOfficeKitDocumentClass, selectPart)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(50), offsetof(struct _LibreOfficeKitDocumentClass, moveSelectedParts)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(51), offsetof(struct _LibreOfficeKitDocumentClass, resizeWindow)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(52), offsetof(struct _LibreOfficeKitDocumentClass, getClipboard)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(53), offsetof(struct _LibreOfficeKitDocumentClass, setClipboard)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(54), offsetof(struct _LibreOfficeKitDocumentClass, getSelectionType)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(55), offsetof(struct _LibreOfficeKitDocumentClass, removeTextContext)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(56), offsetof(struct _LibreOfficeKitDocumentClass, sendDialogEvent)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(57), offsetof(struct _LibreOfficeKitDocumentClass, renderFontOrientation)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(58), offsetof(struct _LibreOfficeKitDocumentClass, paintWindowForView)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(59), offsetof(struct _LibreOfficeKitDocumentClass, completeFunction)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(60), offsetof(struct _LibreOfficeKitDocumentClass, setWindowTextSelection)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(61), offsetof(struct _LibreOfficeKitDocumentClass, sendFormFieldEvent)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(62), offsetof(struct _LibreOfficeKitDocumentClass, setBlockedCommandList)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(63), offsetof(struct _LibreOfficeKitDocumentClass, renderSearchResult)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(64), + offsetof(struct _LibreOfficeKitDocumentClass, sendContentControlEvent)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(65), offsetof(struct _LibreOfficeKitDocumentClass, getSelectionTypeAndText)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(66), offsetof(struct _LibreOfficeKitDocumentClass, getDataArea)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(67), offsetof(struct _LibreOfficeKitDocumentClass, getEditMode)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(68), + offsetof(struct _LibreOfficeKitDocumentClass, setViewTimezone)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(69), + offsetof(struct _LibreOfficeKitDocumentClass, setAccessibilityState)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(70), + offsetof(struct _LibreOfficeKitDocumentClass, getA11yFocusedParagraph)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(71), + offsetof(struct _LibreOfficeKitDocumentClass, getA11yCaretPosition)); + + // As above + CPPUNIT_ASSERT_EQUAL(documentClassOffset(72), sizeof(struct _LibreOfficeKitDocumentClass)); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(DesktopLOKTest); + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/qa/unit/data/desktop-dialogs-test.txt b/desktop/qa/unit/data/desktop-dialogs-test.txt new file mode 100644 index 0000000000..a266956871 --- /dev/null +++ b/desktop/qa/unit/data/desktop-dialogs-test.txt @@ -0,0 +1,44 @@ +# -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +# This file contains all dialogs that the unit tests in the module +# will work on if it is in script mode. It will read one-by-one, +# try to open it and create a screenshot that will be saved in +# workdir/screenshots using the pattern of the ui-file name. +# +# Syntax: +# - empty lines are allowed +# - lines starting with '#' are treated as comment +# - all other lines should contain a *.ui filename in the same +# notation as in the dialog constructors (see code) + +# +# The 'known' dialogs which have a hard-coded representation +# in registerKnownDialogsByID/createDialogByID +# + +# No known dialogs in desktop for now + +# +# Dialogs without a hard-coded representation. These will +# be visualized using a fallback based on weld::Builder +# + +# currently deactivated, leads to problems and the test to not work +# This is typically a hint that these should be hard-coded in the +# test case since they need some document and model data to work +# desktop/ui/extensionmanager.ui + +desktop/ui/dependenciesdialog.ui +desktop/ui/updaterequireddialog.ui +desktop/ui/showlicensedialog.ui +desktop/ui/updatedialog.ui +desktop/ui/updateinstalldialog.ui +desktop/ui/licensedialog.ui +desktop/ui/installforalldialog.ui diff --git a/desktop/qa/unit/desktop-dialogs-test.cxx b/desktop/qa/unit/desktop-dialogs-test.cxx new file mode 100644 index 0000000000..56c39eb710 --- /dev/null +++ b/desktop/qa/unit/desktop-dialogs-test.cxx @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> +#include <test/screenshot_test.hxx> +#include <vcl/abstdlg.hxx> + +using namespace ::com::sun::star; + +/// Test opening a dialog in desktop +class DesktopDialogsTest : public ScreenshotTest +{ +private: + /// helper method to populate KnownDialogs, called in setUp(). Needs to be + /// written and has to add entries to KnownDialogs + virtual void registerKnownDialogsByID(mapType& rKnownDialogs) override; + + /// dialog creation for known dialogs by ID. Has to be implemented for + /// each registered known dialog + virtual VclPtr<VclAbstractDialog> createDialogByID(sal_uInt32 nID) override; + +public: + DesktopDialogsTest(); + + // try to open a dialog + void openAnyDialog(); + + CPPUNIT_TEST_SUITE(DesktopDialogsTest); + CPPUNIT_TEST(openAnyDialog); + CPPUNIT_TEST_SUITE_END(); +}; + +DesktopDialogsTest::DesktopDialogsTest() {} + +void DesktopDialogsTest::registerKnownDialogsByID(mapType& /*rKnownDialogs*/) +{ + // fill map of known dialogs +} + +VclPtr<VclAbstractDialog> DesktopDialogsTest::createDialogByID(sal_uInt32 /*nID*/) +{ + return nullptr; +} + +void DesktopDialogsTest::openAnyDialog() +{ + /// process input file containing the UXMLDescriptions of the dialogs to dump + processDialogBatchFile(u"desktop/qa/unit/data/desktop-dialogs-test.txt"); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(DesktopDialogsTest); + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/qa/unit/desktop-lok-init.cxx b/desktop/qa/unit/desktop-lok-init.cxx new file mode 100644 index 0000000000..49971afc2e --- /dev/null +++ b/desktop/qa/unit/desktop-lok-init.cxx @@ -0,0 +1,160 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <cppunit/TestFixture.h> +#include <cppunit/plugin/TestPlugIn.h> +#include <cppunit/extensions/HelperMacros.h> +#include <comphelper/anytostring.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/sequence.hxx> + +#include <tools/color.hxx> + +#include <lib/init.hxx> + +#include <com/sun/star/table/BorderLine2.hpp> +#include <com/sun/star/table/BorderLineStyle.hpp> + +using namespace css; + +/// Unit tests for desktop/source/lib/init.cxx internals. +class LOKInitTest : public ::CppUnit::TestFixture +{ +public: + LOKInitTest() {} + + void testJsonToPropertyValues(); + void testJsonToPropertyValuesBorder(); + + CPPUNIT_TEST_SUITE(LOKInitTest); + CPPUNIT_TEST(testJsonToPropertyValues); + CPPUNIT_TEST(testJsonToPropertyValuesBorder); + CPPUNIT_TEST_SUITE_END(); +}; + +namespace +{ +void assertSequencesEqual(const uno::Sequence<beans::PropertyValue>& expected, + const uno::Sequence<beans::PropertyValue>& actual) +{ + CPPUNIT_ASSERT_EQUAL_MESSAGE("The sequences should have the same length", expected.getLength(), + actual.getLength()); + for (int i = 0; i < expected.getLength(); ++i) + { + CPPUNIT_ASSERT_EQUAL(expected[i].Name, actual[i].Name); + CPPUNIT_ASSERT_EQUAL(comphelper::anyToString(expected[i].Value), + comphelper::anyToString(actual[i].Value)); + } +} +} // namespace + +void LOKInitTest::testJsonToPropertyValues() +{ + const char arguments[] = "{" + "\"FileName\":{" + "\"type\":\"string\"," + "\"value\":\"something.odt\"" + "}}"; + + uno::Sequence aArgs{ comphelper::makePropertyValue("FileName", OUString("something.odt")) }; + + assertSequencesEqual( + aArgs, comphelper::containerToSequence(desktop::jsonToPropertyValuesVector(arguments))); +} + +void LOKInitTest::testJsonToPropertyValuesBorder() +{ + const char arguments[] + = "{" + "\"OuterBorder\": {" + "\"type\" : \"[]any\"," + "\"value\" : [" + "{ \"type\" : \"com.sun.star.table.BorderLine2\", \"value\" : { \"Color\" : { \"type\" : " + "\"com.sun.star.util.Color\", \"value\" : 0 }, \"InnerLineWidth\" : { \"type\" : " + "\"short\", \"value\" : 0 }, \"OuterLineWidth\" : { \"type\" : \"short\", \"value\" : 1 " + "}, \"LineDistance\" : { \"type\" : \"short\", \"value\" : 0 }, \"LineStyle\" : { " + "\"type\" : \"short\", \"value\" : 0 }, \"LineWidth\" : { \"type\" : \"unsigned long\", " + "\"value\" : 1 } } }," + "{ \"type\" : \"com.sun.star.table.BorderLine2\", \"value\" : { \"Color\" : { \"type\" : " + "\"com.sun.star.util.Color\", \"value\" : 0 }, \"InnerLineWidth\" : { \"type\" : " + "\"short\", \"value\" : 0 }, \"OuterLineWidth\" : { \"type\" : \"short\", \"value\" : 1 " + "}, \"LineDistance\" : { \"type\" : \"short\", \"value\" : 0 }, \"LineStyle\" : { " + "\"type\" : \"short\", \"value\" : 0 }, \"LineWidth\" : { \"type\" : \"unsigned long\", " + "\"value\" : 1 } } }," + "{ \"type\" : \"com.sun.star.table.BorderLine2\", \"value\" : { \"Color\" : { \"type\" : " + "\"com.sun.star.util.Color\", \"value\" : 0 }, \"InnerLineWidth\" : { \"type\" : " + "\"short\", \"value\" : 0 }, \"OuterLineWidth\" : { \"type\" : \"short\", \"value\" : 1 " + "}, \"LineDistance\" : { \"type\" : \"short\", \"value\" : 0 }, \"LineStyle\" : { " + "\"type\" : \"short\", \"value\" : 0 }, \"LineWidth\" : { \"type\" : \"unsigned long\", " + "\"value\" : 1 } } }," + "{ \"type\" : \"com.sun.star.table.BorderLine2\", \"value\" : { \"Color\" : { \"type\" : " + "\"com.sun.star.util.Color\", \"value\" : 0 }, \"InnerLineWidth\" : { \"type\" : " + "\"short\", \"value\" : 0 }, \"OuterLineWidth\" : { \"type\" : \"short\", \"value\" : 1 " + "}, \"LineDistance\" : { \"type\" : \"short\", \"value\" : 0 }, \"LineStyle\" : { " + "\"type\" : \"short\", \"value\" : 0 }, \"LineWidth\" : { \"type\" : \"unsigned long\", " + "\"value\" : 1 } } }," + "{ \"type\" : \"long\", \"value\" : 0 }," + "{ \"type\" : \"long\", \"value\" : 0 }," + "{ \"type\" : \"long\", \"value\" : 0 }," + "{ \"type\" : \"long\", \"value\" : 0 }," + "{ \"type\" : \"long\", \"value\" : 0 }" + "]" + "}," + "\"InnerBorder\":{" + "\"type\" : \"[]any\"," + "\"value\" : [" + "{ \"type\" : \"com.sun.star.table.BorderLine2\", \"value\" : { \"Color\" : { \"type\" : " + "\"com.sun.star.util.Color\", \"value\" : 0 }, \"InnerLineWidth\" : { \"type\" : " + "\"short\", \"value\" : 0 }, \"OuterLineWidth\" : { \"type\" : \"short\", \"value\" : 1 " + "}, \"LineDistance\" : { \"type\" : \"short\", \"value\" : 0 }, \"LineStyle\" : { " + "\"type\" : \"short\", \"value\" : 0 }, \"LineWidth\" : { \"type\" : \"unsigned long\", " + "\"value\" : 1 } } }," + "{ \"type\" : \"com.sun.star.table.BorderLine2\", \"value\" : { \"Color\" : { \"type\" : " + "\"com.sun.star.util.Color\", \"value\" : 0 }, \"InnerLineWidth\" : { \"type\" : " + "\"short\", \"value\" : 0 }, \"OuterLineWidth\" : { \"type\" : \"short\", \"value\" : 1 " + "}, \"LineDistance\" : { \"type\" : \"short\", \"value\" : 0 }, \"LineStyle\" : { " + "\"type\" : \"short\", \"value\" : 0 }, \"LineWidth\" : { \"type\" : \"unsigned long\", " + "\"value\" : 1 } } }," + "{ \"type\" : \"short\", \"value\" : 0 }," + "{ \"type\" : \"short\", \"value\" : 127 }," + "{ \"type\" : \"long\", \"value\" : 0 }" + "]" + "}}"; + + // see SvxBoxItem::QueryValue for details + table::BorderLine2 aLine(sal_Int32(COL_BLACK), 0, 1, 0, table::BorderLineStyle::SOLID, 1); + uno::Sequence<uno::Any> aOuterSeq{ uno::Any(aLine), // left + uno::Any(aLine), // right + uno::Any(aLine), // bottom + uno::Any(aLine), // top + uno::Any(static_cast<sal_Int32>(0)), + uno::Any(static_cast<sal_Int32>(0)), + uno::Any(static_cast<sal_Int32>(0)), + uno::Any(static_cast<sal_Int32>(0)), + uno::Any(static_cast<sal_Int32>(0)) }; + + // see SvxBoxInfoItem::QueryValue() for details + uno::Sequence<uno::Any> aInnerSeq{ uno::Any(aLine), // horizontal + uno::Any(aLine), // vertical + uno::Any(static_cast<sal_Int16>(0)), + uno::Any(static_cast<sal_Int16>(0x7F)), + uno::Any(static_cast<sal_Int32>(0)) }; + + uno::Sequence aArgs{ comphelper::makePropertyValue("OuterBorder", aOuterSeq), + comphelper::makePropertyValue("InnerBorder", aInnerSeq) }; + + assertSequencesEqual( + aArgs, comphelper::containerToSequence(desktop::jsonToPropertyValuesVector(arguments))); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(LOKInitTest); + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |