summaryrefslogtreecommitdiffstats
path: root/sfx2/qa
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /sfx2/qa
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sfx2/qa')
-rw-r--r--sfx2/qa/complex/sfx2/DocumentEvents.java221
-rw-r--r--sfx2/qa/complex/sfx2/DocumentMetadataAccess.java1228
-rw-r--r--sfx2/qa/complex/sfx2/DocumentProperties.java516
-rw-r--r--sfx2/qa/complex/sfx2/GlobalEventBroadcaster.java249
-rw-r--r--sfx2/qa/complex/sfx2/UndoManager.java1460
-rw-r--r--sfx2/qa/complex/sfx2/testdocuments/CUSTOM.odtbin0 -> 1021 bytes
-rw-r--r--sfx2/qa/complex/sfx2/testdocuments/TEST.odtbin0 -> 13803 bytes
-rw-r--r--sfx2/qa/complex/sfx2/testdocuments/TESTRDFA.odtbin0 -> 7540 bytes
-rw-r--r--sfx2/qa/complex/sfx2/testdocuments/empty.rdf13
-rw-r--r--sfx2/qa/complex/sfx2/tools/TestDocument.java34
-rw-r--r--sfx2/qa/complex/sfx2/tools/WriterHelper.java210
-rw-r--r--sfx2/qa/complex/sfx2/undo/CalcDocumentTest.java113
-rw-r--r--sfx2/qa/complex/sfx2/undo/ChartDocumentTest.java257
-rw-r--r--sfx2/qa/complex/sfx2/undo/DocumentTest.java77
-rw-r--r--sfx2/qa/complex/sfx2/undo/DocumentTestBase.java44
-rw-r--r--sfx2/qa/complex/sfx2/undo/DrawDocumentTest.java35
-rw-r--r--sfx2/qa/complex/sfx2/undo/DrawingOrPresentationDocumentTest.java207
-rw-r--r--sfx2/qa/complex/sfx2/undo/ImpressDocumentTest.java35
-rw-r--r--sfx2/qa/complex/sfx2/undo/WriterDocumentTest.java121
-rw-r--r--sfx2/qa/cppunit/data/reload-page.odgbin0 -> 8824 bytes
-rw-r--r--sfx2/qa/cppunit/doc.cxx151
-rw-r--r--sfx2/qa/cppunit/misc/hello.odtbin0 -> 8159 bytes
-rw-r--r--sfx2/qa/cppunit/test_classification.cxx112
-rw-r--r--sfx2/qa/cppunit/test_controlleritem.cxx58
-rw-r--r--sfx2/qa/cppunit/test_metadatable.cxx248
-rw-r--r--sfx2/qa/cppunit/test_misc.cxx201
-rw-r--r--sfx2/qa/cppunit/view.cxx64
-rw-r--r--sfx2/qa/python/check_sidebar.py169
-rw-r--r--sfx2/qa/python/check_sidebar_registry.py89
-rw-r--r--sfx2/qa/uitest/doc/data/pdf-sign.pdfbin0 -> 2235 bytes
-rw-r--r--sfx2/qa/uitest/doc/objserv.py24
-rw-r--r--sfx2/qa/unit/data/sfx2-dialogs-test.txt72
-rw-r--r--sfx2/qa/unit/sfx2-dialogs-test.cxx58
-rw-r--r--sfx2/qa/unoapi/knownissues.xcl5
-rw-r--r--sfx2/qa/unoapi/sfx.sce4
-rw-r--r--sfx2/qa/unoapi/testdocuments/SfxStandaloneDocInfoObject.sdwbin0 -> 8192 bytes
-rw-r--r--sfx2/qa/unoapi/testdocuments/report.stwbin0 -> 11186 bytes
-rw-r--r--sfx2/qa/unoapi/testdocuments/report2.stwbin0 -> 11000 bytes
38 files changed, 6075 insertions, 0 deletions
diff --git a/sfx2/qa/complex/sfx2/DocumentEvents.java b/sfx2/qa/complex/sfx2/DocumentEvents.java
new file mode 100644
index 0000000000..32c7ba5ae1
--- /dev/null
+++ b/sfx2/qa/complex/sfx2/DocumentEvents.java
@@ -0,0 +1,221 @@
+package complex.sfx2;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.openoffice.test.OfficeConnection;
+import org.openoffice.test.tools.OfficeDocument;
+
+import com.sun.star.document.DocumentEvent;
+import com.sun.star.document.XDocumentEventBroadcaster;
+import com.sun.star.document.XDocumentEventListener;
+import com.sun.star.lang.EventObject;
+import com.sun.star.lang.XEventListener;
+import com.sun.star.lang.XMultiServiceFactory;
+import com.sun.star.uno.UnoRuntime;
+import com.sun.star.util.CloseVetoException;
+import com.sun.star.util.XCloseListener;
+import com.sun.star.util.XCloseable;
+
+public class DocumentEvents
+{
+ private static final OfficeConnection m_connection = new OfficeConnection();
+
+ @BeforeClass
+ public static void setUpConnection() throws Exception
+ {
+ m_connection.setUp();
+ }
+
+
+ @AfterClass
+ public static void tearDownConnection() throws InterruptedException, com.sun.star.uno.Exception
+ {
+ m_connection.tearDown();
+ }
+
+ @Before
+ public void beforeTest() throws Exception
+ {
+ final XMultiServiceFactory xMSF1 = UnoRuntime.queryInterface(
+ XMultiServiceFactory.class, m_connection.getComponentContext().getServiceManager() );
+ m_document = OfficeDocument.blankTextDocument( xMSF1 );
+ }
+
+ @After
+ public void afterTest()
+ {
+ if ( m_document != null )
+ {
+ assertTrue( "closing the test document failed", m_document.close() );
+ m_document = null;
+ }
+ }
+
+ /**
+ * sets up the environment for a test which checks the behavior upon closing a doc
+ */
+ private void impl_setupDocCloseTest()
+ {
+ m_observedCloseEvents.clear();
+
+ final XDocumentEventBroadcaster docEventBroadcaster = UnoRuntime.queryInterface(
+ XDocumentEventBroadcaster.class, m_document.getDocument() );
+ docEventBroadcaster.addDocumentEventListener( new DocumentEventListener() );
+
+ final XCloseable docCloseable = UnoRuntime.queryInterface( XCloseable.class,
+ m_document.getDocument() );
+ docCloseable.addCloseListener( new CloseListener() );
+
+ m_document.getDocument().addEventListener( new DocDisposeListener() );
+ }
+
+ /**
+ * sets up the environment for a test which checks the behavior upon closing a doc
+ */
+ private void impl_tearDownDocCloseTest( final String i_docCloseMethod )
+ {
+ synchronized( m_document )
+ {
+ try
+ {
+ m_document.wait(10000);
+ }
+ catch (InterruptedException ex)
+ {
+ // don't continue the test if somebody interrupted us ...
+ return;
+ }
+ }
+
+ m_document = null;
+ synchronized( m_observedCloseEvents )
+ {
+ assertArrayEquals(
+ "wrong order of events when closing a doc " + i_docCloseMethod,
+ new CloseEventType[] { CloseEventType.OnUnload, CloseEventType.NotifyClosing, CloseEventType.Disposing },
+ m_observedCloseEvents.toArray( new CloseEventType[0] )
+ );
+ }
+ }
+
+ @Test
+ public void testCloseWinEvents() throws Exception
+ {
+ impl_setupDocCloseTest();
+ m_document.getCurrentView().dispatch( ".uno:CloseWin" );
+ impl_tearDownDocCloseTest( "via .uno:CloseWin" );
+ }
+
+ //@Test
+ public void testCloseDocEvents() throws Exception
+ {
+ impl_setupDocCloseTest();
+ m_document.getCurrentView().dispatch( ".uno:CloseDoc" );
+ impl_tearDownDocCloseTest( "via .uno:CloseDoc" );
+ }
+
+ //@Test
+ public void testCloseByAPI()
+ {
+ impl_setupDocCloseTest();
+ // closing the doc by API is synchronous, so do this in a separate thread, else we will get a deadlock
+ // when the document tries to call back our listener (well, I admit I didn't understand *why* we get this
+ // deadlock ... :-\ )
+ (new DocCloser()).start();
+ impl_tearDownDocCloseTest( "by API" );
+ }
+
+ private class DocumentEventListener implements XDocumentEventListener
+ {
+
+ public void documentEventOccured( DocumentEvent i_documentEvent )
+ {
+ if ( i_documentEvent.EventName.equals( "OnUnload" ) )
+ {
+ synchronized( m_observedCloseEvents )
+ {
+ m_observedCloseEvents.add( CloseEventType.OnUnload );
+ }
+ }
+ }
+
+ public void disposing(EventObject eo)
+ {
+ // not interested in
+ }
+ }
+
+ private class CloseListener implements XCloseListener
+ {
+
+ public void queryClosing(EventObject eo, boolean bln) throws CloseVetoException
+ {
+ // not interested in
+ }
+
+ public void notifyClosing(EventObject eo)
+ {
+ synchronized( m_observedCloseEvents )
+ {
+ m_observedCloseEvents.add( CloseEventType.NotifyClosing );
+ }
+ }
+
+ public void disposing(EventObject eo)
+ {
+ // not interested in
+ }
+ }
+
+ private class DocDisposeListener implements XEventListener
+ {
+ public void disposing(EventObject eo)
+ {
+ synchronized( m_observedCloseEvents )
+ {
+ m_observedCloseEvents.add( CloseEventType.Disposing );
+ }
+ synchronized ( m_document )
+ {
+ m_document.notifyAll();
+ }
+ }
+ }
+
+ private class DocCloser extends Thread
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ final XCloseable docCloseable = UnoRuntime.queryInterface(XCloseable.class, m_document.getDocument());
+ docCloseable.close(true);
+ }
+ catch (CloseVetoException ex)
+ {
+ Logger.getLogger(DocumentEvents.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+ }
+
+ private enum CloseEventType
+ {
+ OnUnload,
+ NotifyClosing,
+ Disposing
+ }
+
+ private OfficeDocument m_document = null;
+ final private ArrayList< CloseEventType > m_observedCloseEvents = new ArrayList<DocumentEvents.CloseEventType>();
+}
diff --git a/sfx2/qa/complex/sfx2/DocumentMetadataAccess.java b/sfx2/qa/complex/sfx2/DocumentMetadataAccess.java
new file mode 100644
index 0000000000..5697bba07e
--- /dev/null
+++ b/sfx2/qa/complex/sfx2/DocumentMetadataAccess.java
@@ -0,0 +1,1228 @@
+/*
+ * 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 .
+ */
+
+package complex.sfx2;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import helper.StreamSimulator;
+
+import lib.TestParameters;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.openoffice.test.OfficeConnection;
+
+import com.sun.star.beans.Pair;
+import com.sun.star.beans.PropertyValue;
+import com.sun.star.beans.StringPair;
+import com.sun.star.beans.XPropertySet;
+import com.sun.star.container.XEnumeration;
+import com.sun.star.container.XEnumerationAccess;
+import com.sun.star.frame.XStorable;
+import com.sun.star.io.XInputStream;
+import com.sun.star.lang.IllegalArgumentException;
+import com.sun.star.lang.XComponent;
+import com.sun.star.lang.XMultiServiceFactory;
+import com.sun.star.lang.XServiceInfo;
+import com.sun.star.rdf.BlankNode;
+import com.sun.star.rdf.FileFormat;
+import com.sun.star.rdf.Literal;
+import com.sun.star.rdf.Statement;
+import com.sun.star.rdf.URI;
+import com.sun.star.rdf.URIs;
+import com.sun.star.rdf.XBlankNode;
+import com.sun.star.rdf.XDocumentMetadataAccess;
+import com.sun.star.rdf.XDocumentRepository;
+import com.sun.star.rdf.XLiteral;
+import com.sun.star.rdf.XMetadatable;
+import com.sun.star.rdf.XNamedGraph;
+import com.sun.star.rdf.XNode;
+import com.sun.star.rdf.XQuerySelectResult;
+import com.sun.star.rdf.XRepository;
+import com.sun.star.rdf.XRepositorySupplier;
+import com.sun.star.rdf.XURI;
+import com.sun.star.text.XText;
+import com.sun.star.text.XTextDocument;
+import com.sun.star.text.XTextRange;
+import com.sun.star.uno.UnoRuntime;
+import com.sun.star.uno.XComponentContext;
+import com.sun.star.util.XCloseable;
+import complex.sfx2.tools.TestDocument;
+
+/**
+ * Test case for interface com.sun.star.rdf.XDocumentMetadataAccess
+ * Currently, this service is implemented in
+ * sfx2/source/doc/DocumentMetadataAccess.cxx
+ *
+ * Actually, this is not a service, so we need to create a document and
+ * go from there...
+ *
+ */
+public class DocumentMetadataAccess
+{
+ XMultiServiceFactory xMSF;
+ XComponentContext xContext;
+ String tempDir;
+
+ String nsRDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+ String nsRDFS = "http://www.w3.org/2000/01/rdf-schema#";
+ String nsPkg="http://docs.oasis-open.org/opendocument/meta/package/common#";
+ String nsODF ="http://docs.oasis-open.org/opendocument/meta/package/odf#";
+
+ XURI foo;
+ XURI bar;
+ XURI baz;
+
+ static XURI rdf_type;
+ static XURI rdfs_label;
+ static XURI pkg_Document;
+ static XURI pkg_hasPart;
+ static XURI pkg_MetadataFile;
+ static XURI odf_ContentFile;
+ static XURI odf_StylesFile;
+ static XURI odf_Element;
+ static XBlankNode blank1;
+ static XBlankNode blank2;
+ static XBlankNode blank3;
+ static XBlankNode blank4;
+ static String manifestPath = "manifest.rdf";
+ static String contentPath = "content.xml";
+ static String stylesPath = "styles.xml";
+ static String fooPath = "foo.rdf";
+ static String fooBarPath = "meta/foo/bar.rdf";
+
+ XRepository xRep;
+ XRepositorySupplier xRS;
+ XDocumentMetadataAccess xDMA;
+
+ /**
+ * The test parameters
+ */
+ private static TestParameters param = null;
+
+ @Before public void before() throws Exception
+ {
+ xMSF = getMSF();
+ param = new TestParameters();
+ param.put("ServiceFactory", xMSF); // important for param.getMSF()
+
+ assertNotNull("could not create MultiServiceFactory.", xMSF);
+ XPropertySet xPropertySet = UnoRuntime.queryInterface(XPropertySet.class, xMSF);
+ Object defaultCtx = xPropertySet.getPropertyValue("DefaultContext");
+ xContext = UnoRuntime.queryInterface(XComponentContext.class, defaultCtx);
+ assertNotNull("could not get component context.", xContext);
+
+ tempDir = util.utils.getOfficeTemp/*Dir*/(xMSF);
+ System.out.println("tempdir: " + tempDir);
+
+ foo = URI.create(xContext, "uri:foo");
+ assertNotNull("foo", foo);
+ bar = URI.create(xContext, "uri:bar");
+ assertNotNull("bar", bar);
+ baz = URI.create(xContext, "uri:baz");
+ assertNotNull("baz", baz);
+
+ blank1 = BlankNode.create(xContext, "_:1");
+ assertNotNull("blank1", blank1);
+ blank2 = BlankNode.create(xContext, "_:2");
+ assertNotNull("blank2", blank2);
+ blank3 = BlankNode.create(xContext, "_:3");
+ assertNotNull("blank3", blank3);
+ blank4 = BlankNode.create(xContext, "_:4");
+ assertNotNull("blank4", blank4);
+ rdf_type = URI.createKnown(xContext, URIs.RDF_TYPE);
+ assertNotNull("rdf_type", rdf_type);
+ rdfs_label = URI.createKnown(xContext, URIs.RDFS_LABEL);
+ assertNotNull("rdfs_label", rdfs_label);
+ pkg_Document = URI.createKnown(xContext, URIs.PKG_DOCUMENT);
+ assertNotNull("pkg_Document", pkg_Document);
+ pkg_hasPart = URI.createKnown(xContext, URIs.PKG_HASPART);
+ assertNotNull("pkg_hasPart", pkg_hasPart);
+ pkg_MetadataFile = URI.createKnown(xContext, URIs.PKG_METADATAFILE);
+ assertNotNull("pkg_MetadataFile", pkg_MetadataFile);
+ odf_ContentFile = URI.createKnown(xContext, URIs.ODF_CONTENTFILE);
+ assertNotNull("odf_ContentFile", odf_ContentFile);
+ odf_StylesFile = URI.createKnown(xContext, URIs.ODF_STYLESFILE);
+ assertNotNull("odf_StylesFile", odf_StylesFile);
+ odf_Element = URI.createKnown(xContext, URIs.ODF_ELEMENT);
+ assertNotNull("odf_Element", odf_Element);
+ }
+
+ @After public void after()
+ {
+ xRep = null;
+ xRS = null;
+ xDMA = null;
+ }
+
+ @Test public void check() throws Exception
+ {
+ XComponent xComp = null;
+ XComponent xComp2 = null;
+ try {
+ XEnumeration xStmtsEnum;
+ XNamedGraph xManifest;
+
+ System.out.println("Creating document with Repository...");
+
+ // we cannot create a XDMA directly, we must create
+ // a document and get it from there :(
+ // create document
+ PropertyValue[] loadProps = new PropertyValue[1];
+ loadProps[0] = new PropertyValue();
+ loadProps[0].Name = "Hidden";
+ loadProps[0].Value = true;
+ xComp = util.DesktopTools.openNewDoc(xMSF, "swriter", loadProps);
+ XTextDocument xText = UnoRuntime.queryInterface(XTextDocument.class, xComp);
+
+ XRepositorySupplier xRepoSupplier = UnoRuntime.queryInterface(XRepositorySupplier.class, xComp);
+ assertNotNull("xRS null", xRepoSupplier);
+ XDocumentMetadataAccess xDocMDAccess = UnoRuntime.queryInterface(XDocumentMetadataAccess.class, xRepoSupplier);
+ assertNotNull("xDMA null", xDocMDAccess);
+ xRep = xRepoSupplier.getRDFRepository();
+ assertNotNull("xRep null", xRep);
+
+ System.out.println("...done");
+
+ System.out.println("Checking that new repository is initialized...");
+
+ XURI xBaseURI = xDocMDAccess;
+ String baseURI = xBaseURI.getStringValue();
+ assertNotNull("new: baseURI", xBaseURI );
+ assertTrue("new: baseURI", !xBaseURI.getStringValue().equals(""));
+
+ assertTrue("new: # graphs", 1 == xRep.getGraphNames().length);
+ XURI manifest = URI.createNS(xContext, xBaseURI.getStringValue(),
+ manifestPath);
+ xManifest = xRep.getGraph(manifest);
+ assertTrue("new: manifest graph", null != xManifest);
+
+ Statement[] manifestStmts = getManifestStmts(xBaseURI);
+ xStmtsEnum = xRep.getStatements(null, null, null);
+ assertTrue("new: manifest graph", eq(xStmtsEnum, manifestStmts));
+
+ System.out.println("...done");
+
+ System.out.println("Checking some invalid args...");
+
+ String content = "behold, for I am the content.";
+ new TestRange(content);
+
+ try {
+ xDocMDAccess.getElementByURI(null);
+ fail("getElementByURI: null allowed");
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+ try {
+ xDocMDAccess.getMetadataGraphsWithType(null);
+ fail("getMetadataGraphsWithType: null URI allowed");
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+ try {
+ xDocMDAccess.addMetadataFile("", new XURI[0]);
+ fail("addMetadataFile: empty filename allowed");
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+ try {
+ xDocMDAccess.addMetadataFile("/foo", new XURI[0]);
+ fail("addMetadataFile: absolute filename allowed");
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+ try {
+ xDocMDAccess.addMetadataFile("fo\"o", new XURI[0]);
+ fail("addMetadataFile: invalid filename allowed");
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+ try {
+ xDocMDAccess.addMetadataFile("../foo", new XURI[0]);
+ fail("addMetadataFile: filename with .. allowed");
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+ try {
+ xDocMDAccess.addMetadataFile("foo/../../bar", new XURI[0]);
+ fail("addMetadataFile: filename with nest .. allowed");
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+ try {
+ xDocMDAccess.addMetadataFile("foo/././bar", new XURI[0]);
+ fail("addMetadataFile: filename with nest . allowed");
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+ try {
+ xDocMDAccess.addMetadataFile("content.xml", new XURI[0]);
+ fail("addMetadataFile: content.xml allowed");
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+ try {
+ xDocMDAccess.addMetadataFile("styles.xml", new XURI[0]);
+ fail("addMetadataFile: styles.xml allowed");
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+ try {
+ xDocMDAccess.addMetadataFile("meta.xml", new XURI[0]);
+ fail("addMetadataFile: meta.xml allowed");
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+ try {
+ xDocMDAccess.addMetadataFile("settings.xml", new XURI[0]);
+ fail("addMetadataFile: settings.xml allowed");
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+ try {
+ xDocMDAccess.importMetadataFile(FileFormat.RDF_XML, null, "foo",
+ foo, new XURI[0]);
+ fail("importMetadataFile: null stream allowed");
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+
+ final String sEmptyRDF = TestDocument.getUrl("empty.rdf");
+ try {
+ XInputStream xFooIn = new StreamSimulator(sEmptyRDF, true, param);
+ xDocMDAccess.importMetadataFile(FileFormat.RDF_XML, xFooIn, "",
+ foo, new XURI[0]);
+ fail("importMetadataFile: empty filename allowed");
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+ try {
+ XInputStream xFooIn =
+ new StreamSimulator(sEmptyRDF, true, param);
+ xDocMDAccess.importMetadataFile(FileFormat.RDF_XML, xFooIn, "meta.xml",
+ foo, new XURI[0]);
+ fail("importMetadataFile: meta.xml filename allowed");
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+ try {
+ XInputStream xFooIn =
+ new StreamSimulator(sEmptyRDF, true, param);
+ xDocMDAccess.importMetadataFile(FileFormat.RDF_XML,
+ xFooIn, "foo", null, new XURI[0]);
+ fail("importMetadataFile: null base URI allowed");
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+ try {
+ XInputStream xFooIn =
+ new StreamSimulator(sEmptyRDF, true, param);
+ xDocMDAccess.importMetadataFile(FileFormat.RDF_XML,
+ xFooIn, "foo", rdf_type, new XURI[0]);
+ fail("importMetadataFile: non-absolute base URI allowed");
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+ try {
+ xDocMDAccess.removeMetadataFile(null);
+ fail("removeMetadataFile: null URI allowed");
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+ try {
+ xDocMDAccess.addContentOrStylesFile("");
+ fail("addContentOrStylesFile: empty filename allowed");
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+ try {
+ xDocMDAccess.addContentOrStylesFile("/content.xml");
+ fail("addContentOrStylesFile: absolute filename allowed");
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+ try {
+ xDocMDAccess.addContentOrStylesFile("foo.rdf");
+ fail("addContentOrStylesFile: invalid filename allowed");
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+ try {
+ xDocMDAccess.removeContentOrStylesFile("");
+ fail("removeContentOrStylesFile: empty filename allowed");
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+ try {
+ xDocMDAccess.loadMetadataFromStorage(null, foo, null);
+ fail("loadMetadataFromStorage: null storage allowed");
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+ try {
+ xDocMDAccess.storeMetadataToStorage(null/*, base*/);
+ fail("storeMetadataToStorage: null storage allowed");
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+ try {
+ xDocMDAccess.loadMetadataFromMedium(new PropertyValue[0]);
+ fail("loadMetadataFromMedium: empty medium allowed");
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+ try {
+ xDocMDAccess.storeMetadataToMedium(new PropertyValue[0]);
+ fail("storeMetadataToMedium: empty medium allowed");
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+
+ System.out.println("...done");
+
+ System.out.println("Checking file addition/removal...");
+
+ xDocMDAccess.removeContentOrStylesFile(contentPath);
+ xStmtsEnum = xManifest.getStatements(null, null, null);
+ assertTrue("removeContentOrStylesFile (content)",
+ eq(xStmtsEnum, new Statement[] {
+ manifestStmts[0], manifestStmts[2], manifestStmts[4]
+ }));
+
+ xDocMDAccess.addContentOrStylesFile(contentPath);
+ xStmtsEnum = xManifest.getStatements(null, null, null);
+ assertTrue("addContentOrStylesFile (content)",
+ eq(xStmtsEnum, manifestStmts));
+
+ xDocMDAccess.removeContentOrStylesFile(stylesPath);
+ xStmtsEnum = xManifest.getStatements(null, null, null);
+ assertTrue("removeContentOrStylesFile (styles)",
+ eq(xStmtsEnum, new Statement[] {
+ manifestStmts[0], manifestStmts[1], manifestStmts[3]
+ }));
+
+ xDocMDAccess.addContentOrStylesFile(stylesPath);
+ xStmtsEnum = xManifest.getStatements(null, null, null);
+ assertTrue("addContentOrStylesFile (styles)",
+ eq(xStmtsEnum, manifestStmts));
+
+ XURI xFoo = URI.createNS(xContext, xBaseURI.getStringValue(),
+ fooPath);
+ Statement xM_BaseHaspartFoo =
+ new Statement(xBaseURI, pkg_hasPart, xFoo, manifest);
+ Statement xM_FooTypeMetadata =
+ new Statement(xFoo, rdf_type, pkg_MetadataFile, manifest);
+ Statement xM_FooTypeBar =
+ new Statement(xFoo, rdf_type, bar, manifest);
+ xDocMDAccess.addMetadataFile(fooPath, new XURI[] { bar });
+ xStmtsEnum = xManifest.getStatements(null, null, null);
+ assertTrue("addMetadataFile",
+ eq(xStmtsEnum, merge(manifestStmts, new Statement[] {
+ xM_BaseHaspartFoo, xM_FooTypeMetadata, xM_FooTypeBar
+ })));
+
+ XURI[] graphsBar = xDocMDAccess.getMetadataGraphsWithType(bar);
+ assertTrue("getMetadataGraphsWithType",
+ graphsBar.length == 1 && eq(graphsBar[0], xFoo));
+
+
+ xDocMDAccess.removeMetadataFile(xFoo);
+ xStmtsEnum = xManifest.getStatements(null, null, null);
+ assertTrue("removeMetadataFile",
+ eq(xStmtsEnum, manifestStmts));
+
+ System.out.println("...done");
+
+ System.out.println("Checking mapping...");
+
+ XEnumerationAccess xTextEnum = UnoRuntime.queryInterface(XEnumerationAccess.class, xText.getText());
+ Object o = xTextEnum.createEnumeration().nextElement();
+ XMetadatable xMeta1 = UnoRuntime.queryInterface(XMetadatable.class, o);
+
+ XMetadatable xMeta;
+ xMeta = xDocMDAccess.getElementByURI(xMeta1);
+ assertTrue("getElementByURI: null", null != xMeta);
+ String XmlId = xMeta.getMetadataReference().Second;
+ String XmlId1 = xMeta1.getMetadataReference().Second;
+ assertTrue("getElementByURI: no xml id", !XmlId.equals(""));
+ assertTrue("getElementByURI: different xml id", XmlId.equals(XmlId1));
+
+ System.out.println("...done");
+
+ System.out.println("Checking storing and loading...");
+
+ XURI xFoobar = URI.createNS(xContext, xBaseURI.getStringValue(),
+ fooBarPath);
+ Statement[] metadataStmts = getMetadataFileStmts(xBaseURI,
+ fooBarPath);
+ xDocMDAccess.addMetadataFile(fooBarPath, new XURI[0]);
+ xStmtsEnum = xRep.getStatements(null, null, null);
+ assertTrue("addMetadataFile",
+ eq(xStmtsEnum, merge(manifestStmts, metadataStmts )));
+
+ Statement xFoobar_FooBarFoo =
+ new Statement(foo, bar, foo, xFoobar);
+ xRep.getGraph(xFoobar).addStatement(foo, bar, foo);
+ xStmtsEnum = xRep.getStatements(null, null, null);
+ assertTrue("addStatement",
+ eq(xStmtsEnum, merge(manifestStmts, merge(metadataStmts,
+ new Statement[] { xFoobar_FooBarFoo }))));
+
+ PropertyValue noMDNoContentFile = new PropertyValue();
+ noMDNoContentFile.Name = "URL";
+ noMDNoContentFile.Value = TestDocument.getUrl("CUSTOM.odt");
+ PropertyValue noMDFile = new PropertyValue();
+ noMDFile.Name = "URL";
+ noMDFile.Value = TestDocument.getUrl("TEST.odt");
+ PropertyValue file = new PropertyValue();
+ file.Name = "URL";
+ file.Value = tempDir + "TESTDMA.odt";
+ PropertyValue mimetype = new PropertyValue();
+ mimetype.Name = "MediaType";
+ mimetype.Value = "application/vnd.oasis.opendocument.text";
+ PropertyValue[] argsEmptyNoContent = { mimetype, noMDNoContentFile};
+ PropertyValue[] argsEmpty = { mimetype, noMDFile };
+ PropertyValue[] args = { mimetype, file };
+
+ xStmtsEnum = xRep.getStatements(null, null, null);
+ XURI[] graphs = xRep.getGraphNames();
+
+ xDocMDAccess.storeMetadataToMedium(args);
+
+ // this should re-init
+ xDocMDAccess.loadMetadataFromMedium(argsEmptyNoContent);
+ xRep = xRepoSupplier.getRDFRepository();
+ assertTrue("xRep null", null != xRep);
+ assertTrue("baseURI still tdoc?",
+ !baseURI.equals(xDocMDAccess.getStringValue()));
+ Statement[] manifestStmts2 = getManifestStmts(xDocMDAccess);
+ xStmtsEnum = xRep.getStatements(null, null, null);
+ // there is no content or styles file in here, so we have just
+ // the package stmt
+ assertTrue("loadMetadataFromMedium (no metadata, no content)",
+ eq(xStmtsEnum, new Statement[] { manifestStmts2[0] }));
+
+ // this should re-init
+ xDocMDAccess.loadMetadataFromMedium(argsEmpty);
+ xRep = xRepoSupplier.getRDFRepository();
+ assertTrue("xRep null", null != xRep);
+ assertTrue("baseURI still tdoc?",
+ !baseURI.equals(xDocMDAccess.getStringValue()));
+ Statement[] manifestStmts3 = getManifestStmts(xDocMDAccess);
+
+ xStmtsEnum = xRep.getStatements(null, null, null);
+ assertTrue("loadMetadataFromMedium (no metadata)",
+ eq(xStmtsEnum, manifestStmts3));
+
+ xDocMDAccess.loadMetadataFromMedium(args);
+ xRep = xRepoSupplier.getRDFRepository();
+ assertTrue("xRep null", null != xRep);
+ Statement[] manifestStmts4 = getManifestStmts(xDocMDAccess);
+ Statement[] metadataStmts4 = getMetadataFileStmts(xDocMDAccess,
+ fooBarPath);
+
+ xStmtsEnum = xRep.getStatements(null, null, null);
+ assertTrue("some graph(s) not reloaded",
+ graphs.length == xRep.getGraphNames().length);
+
+ XURI xFoobar4 = URI.createNS(xContext, xDocMDAccess.getStringValue(),
+ fooBarPath);
+ Statement xFoobar_FooBarFoo4 =
+ new Statement(foo, bar, foo, xFoobar4);
+ assertTrue("loadMetadataFromMedium (re-load)",
+ eq(xStmtsEnum, merge(manifestStmts4, merge(metadataStmts4,
+ new Statement[] { xFoobar_FooBarFoo4 }))));
+
+ System.out.println("...done");
+
+ System.out.println("Checking storing and loading via model...");
+
+ String f = tempDir + "TESTPARA.odt";
+
+ XStorable xStor = UnoRuntime.queryInterface(XStorable.class, xRepoSupplier);
+
+ xStor.storeToURL(f, new PropertyValue[0]);
+
+ xComp2 = util.DesktopTools.loadDoc(xMSF, f, loadProps);
+
+ XDocumentMetadataAccess xDMA2 = UnoRuntime.queryInterface(XDocumentMetadataAccess.class, xComp2);
+ assertTrue("xDMA2 null", null != xDMA2);
+
+ XRepositorySupplier xRS2 = UnoRuntime.queryInterface(XRepositorySupplier.class, xComp2);
+ assertTrue("xRS2 null", null != xRS2);
+
+ XRepository xRep2 = xRS2.getRDFRepository();
+ assertTrue("xRep2 null", null != xRep2);
+
+ Statement[] manifestStmts5 = getManifestStmts(xDMA2);
+ Statement[] metadataStmts5 = getMetadataFileStmts(xDMA2,
+ fooBarPath);
+ XURI xFoobar5 = URI.createNS(xContext, xDMA2.getStringValue(),
+ fooBarPath);
+ Statement xFoobar_FooBarFoo5 =
+ new Statement(foo, bar, foo, xFoobar5);
+ xStmtsEnum = xRep.getStatements(null, null, null);
+ XEnumeration xStmtsEnum2 = xRep2.getStatements(null, null, null);
+ assertTrue("load: repository differs",
+ eq(xStmtsEnum2, merge(manifestStmts5, merge(metadataStmts5,
+ new Statement[] { xFoobar_FooBarFoo5 }))));
+
+ System.out.println("...done");
+
+ } finally {
+ close(xComp);
+ close(xComp2);
+ }
+ }
+
+ @Test public void checkRDFa() throws Exception
+ {
+ XComponent xComp = null;
+ try {
+ final String file = TestDocument.getUrl("TESTRDFA.odt");
+ xComp = loadRDFa(file);
+ if (xComp != null)
+ {
+ final String sNewFile = tempDir + "TESTRDFA.odt";
+ storeRDFa(xComp, sNewFile);
+ close(xComp);
+
+ xComp = loadRDFa(sNewFile);
+ }
+ } finally {
+ close(xComp);
+ }
+ }
+
+ @Test
+ public void checkTdf123293() throws Exception
+ {
+ XComponent xComp = null;
+ try {
+ xComp = util.DesktopTools.loadDocUsingStream(xMSF, TestDocument.getPath("TESTRDFA.odt"));
+
+ // Metadata was discarded when loading from stream, make sure it's there now
+ XRepositorySupplier xRepoSupplier = UnoRuntime.queryInterface(XRepositorySupplier.class, xComp);
+ assertNotNull("No metadata loaded", xRepoSupplier);
+ } finally {
+ close(xComp);
+ }
+ }
+
+ private void storeRDFa(XComponent xComp, String file) throws com.sun.star.io.IOException
+ {
+ System.out.println("Storing test document...");
+
+ XStorable xStor = UnoRuntime.queryInterface(XStorable.class, xComp);
+
+ xStor.storeToURL(file, new PropertyValue[0]);
+
+ System.out.println("...done");
+ }
+
+ private XComponent loadRDFa(String file) throws Exception
+ {
+ XComponent xComp = null;
+
+ System.out.println("Loading test document...");
+
+ PropertyValue[] loadProps = new PropertyValue[1];
+ loadProps[0] = new PropertyValue();
+ loadProps[0].Name = "Hidden";
+ loadProps[0].Value = true;
+
+
+
+ xComp = util.DesktopTools.loadDoc(xMSF, file, loadProps);
+
+ XRepositorySupplier xRepoSupplier = UnoRuntime.queryInterface(XRepositorySupplier.class, xComp);
+ assertTrue("xRS null", null != xRepoSupplier);
+
+ XDocumentRepository xDocRepository = UnoRuntime.queryInterface(XDocumentRepository.class, xRepoSupplier.getRDFRepository());
+ assertTrue("xRep null", null != xDocRepository);
+
+ XTextDocument xTextDoc = UnoRuntime.queryInterface(XTextDocument.class, xComp);
+
+ XText xText = xTextDoc.getText();
+
+ XEnumerationAccess xEA = UnoRuntime.queryInterface(XEnumerationAccess.class, xText);
+ XEnumeration xEnum = xEA.createEnumeration();
+
+ System.out.println("...done");
+
+ System.out.println("Checking RDFa in loaded test document...");
+
+ XMetadatable xPara;
+ Pair<Statement[], Boolean> result;
+
+ Statement x_FooBarLit1 = new Statement(foo, bar, mkLit("1"), null);
+ xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement());
+ result = xDocRepository.getStatementRDFa(xPara);
+ assertTrue("RDFa: 1",
+ !result.Second &&
+ eq(result.First, new Statement[] {
+ x_FooBarLit1
+ }));
+
+ Statement x_FooBarLit2 = new Statement(foo, bar, mkLit("2"), null);
+ xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement());
+ result = xDocRepository.getStatementRDFa(xPara);
+ assertTrue("RDFa: 2",
+ !result.Second &&
+ eq(result.First, new Statement[] {
+ x_FooBarLit2
+ }));
+
+ Statement x_BlankBarLit3 =
+ new Statement(blank1, bar, mkLit("3"), null);
+ xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement());
+ result = xDocRepository.getStatementRDFa(xPara);
+ assertTrue("RDFa: 3",
+ !result.Second &&
+ eq(result.First, new Statement[] {
+ x_BlankBarLit3
+ }));
+ XBlankNode b3 = UnoRuntime.queryInterface(XBlankNode.class, result.First[0].Subject);
+
+ Statement x_BlankBarLit4 =
+ new Statement(blank2, bar, mkLit("4"), null);
+ xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement());
+ result = xDocRepository.getStatementRDFa(xPara);
+ assertTrue("RDFa: 4",
+ !result.Second &&
+ eq(result.First, new Statement[] {
+ x_BlankBarLit4
+ }));
+ XBlankNode b4 = UnoRuntime.queryInterface(XBlankNode.class, result.First[0].Subject);
+
+ Statement x_BlankBarLit5 =
+ new Statement(blank1, bar, mkLit("5"), null);
+ xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement());
+ result = xDocRepository.getStatementRDFa(xPara);
+ assertTrue("RDFa: 5",
+ !result.Second &&
+ eq(result.First, new Statement[] {
+ x_BlankBarLit5
+ }));
+ XBlankNode b5 = UnoRuntime.queryInterface(XBlankNode.class, result.First[0].Subject);
+
+ assertTrue("RDFa: 3 != 4",
+ !b3.getStringValue().equals(b4.getStringValue()));
+ assertTrue("RDFa: 3 == 5",
+ b3.getStringValue().equals(b5.getStringValue()));
+
+ Statement x_FooBarLit6 = new Statement(foo, bar, mkLit("6"), null);
+ Statement x_FooBazLit6 = new Statement(foo, baz, mkLit("6"), null);
+ xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement());
+ result = xDocRepository.getStatementRDFa(xPara);
+ assertTrue("RDFa: 6",
+ !result.Second &&
+ eq(result.First, new Statement[] {
+ x_FooBarLit6, x_FooBazLit6
+ }));
+
+ Statement x_FooBarLit7 = new Statement(foo, bar, mkLit("7"), null);
+ Statement x_FooBazLit7 = new Statement(foo, baz, mkLit("7"), null);
+ Statement x_FooFooLit7 = new Statement(foo, foo, mkLit("7"), null);
+ xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement());
+ result = xDocRepository.getStatementRDFa(xPara);
+ assertTrue("RDFa: 7",
+ !result.Second &&
+ eq(result.First, new Statement[] {
+ x_FooBarLit7, x_FooBazLit7, x_FooFooLit7
+ }));
+
+ XNode lit = mkLit("a fooish bar");
+ XNode lit_type= mkLit("a fooish bar", bar);
+ Statement x_FooBarLit = new Statement(foo, bar, lit, null);
+ Statement x_FooBarLittype = new Statement(foo, bar, lit_type, null);
+
+ xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement());
+ result = xDocRepository.getStatementRDFa(xPara);
+ assertTrue("RDFa: 8",
+ result.Second &&
+ eq(result.First, new Statement[] {
+ x_FooBarLit
+ }));
+
+ xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement());
+ result = xDocRepository.getStatementRDFa(xPara);
+ assertTrue("RDFa: 9",
+ result.Second &&
+ eq(result.First, new Statement[] {
+ x_FooBarLit
+ }));
+
+ xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement());
+ result = xDocRepository.getStatementRDFa(xPara);
+ assertTrue("RDFa: 10",
+ result.Second &&
+ eq(result.First, new Statement[] {
+ x_FooBarLittype
+ }));
+
+ Statement x_FooBarLit11
+ = new Statement(foo, bar, mkLit("11", bar), null);
+ xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement());
+ result = xDocRepository.getStatementRDFa(xPara);
+ assertTrue("RDFa: 11",
+ !result.Second &&
+ eq(result.First, new Statement[] {
+ x_FooBarLit11
+ }));
+
+ XURI xFile = URI.createNS(xContext, file, "/" + contentPath);
+ Statement x_FileBarLit12 =
+ new Statement(xFile, bar, mkLit("12"), null);
+ xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement());
+ result = xDocRepository.getStatementRDFa(xPara);
+ assertTrue("RDFa: 12",
+ !result.Second &&
+ eq(result.First, new Statement[] {
+ x_FileBarLit12
+ }));
+
+ xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement());
+ result = xDocRepository.getStatementRDFa(xPara);
+ assertTrue("RDFa: 13",
+ result.Second &&
+ eq(result.First, new Statement[] {
+ x_FooBarLit
+ }));
+
+ new Statement(foo, rdfs_label, mkLit("14"), null);
+ xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement());
+ result = xDocRepository.getStatementRDFa(xPara);
+ assertTrue("RDFa: 14",
+ result.Second &&
+ eq(result.First, new Statement[] {
+ /* x_FooLabelLit14 */ x_FooBarLit
+ }));
+
+ xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement());
+ result = xDocRepository.getStatementRDFa(xPara);
+ assertTrue("RDFa: 15", eq(result.First, new Statement[] { } ));
+
+ xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement());
+ result = xDocRepository.getStatementRDFa(xPara);
+ assertTrue("RDFa: 16", eq(result.First, new Statement[] { } ));
+
+ xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement());
+ result = xDocRepository.getStatementRDFa(xPara);
+ assertTrue("RDFa: 17", eq(result.First, new Statement[] { } ));
+
+ xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement());
+ result = xDocRepository.getStatementRDFa(xPara);
+ assertTrue("RDFa: 18", eq(result.First, new Statement[] { } ));
+
+ xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement());
+ result = xDocRepository.getStatementRDFa(xPara);
+ assertTrue("RDFa: 19", eq(result.First, new Statement[] { } ));
+
+ xPara = UnoRuntime.queryInterface(
+ XMetadatable.class, xEnum.nextElement());
+ result = xDocRepository.getStatementRDFa(xPara);
+ assertTrue("RDFa: 20", eq(result.First, new Statement[] { } ));
+
+ xPara = UnoRuntime.queryInterface(
+ XMetadatable.class, xEnum.nextElement());
+ result = xDocRepository.getStatementRDFa(xPara);
+ assertTrue("RDFa: 21", eq(result.First, new Statement[] { } ));
+
+ System.out.println("...done");
+
+ return xComp;
+ }
+
+
+// utilities -------------------------------------------------------------
+
+ static void close(XComponent i_comp)
+ {
+ try {
+ XCloseable xClos = UnoRuntime.queryInterface(XCloseable.class, i_comp);
+ if (xClos != null)
+ {
+ xClos.close(true);
+ }
+ } catch (Exception e) {
+ }
+ }
+
+ XLiteral mkLit(String i_content)
+ {
+ return Literal.create(xContext, i_content);
+ }
+
+ XLiteral mkLit(String i_content, XURI i_uri)
+ {
+ return Literal.createWithType(xContext, i_content, i_uri);
+ }
+
+ static Statement[] merge(Statement[] i_A1, Statement[] i_A2)
+ {
+ // bah, java sucks...
+ Statement[] ret = new Statement[i_A1.length + i_A2.length];
+ for (int i = 0; i < i_A1.length; ++i) {
+ ret[i] = i_A1[i];
+ }
+ for (int i = 0; i < i_A2.length; ++i) {
+ ret[i+i_A1.length] = i_A2[i];
+ }
+ return ret;
+ }
+
+ public static String toS(XNode n) {
+ if (null == n)
+ {
+ return "< null >";
+ }
+ return n.getStringValue();
+ }
+
+ static boolean isBlank(XNode i_node)
+ {
+ XBlankNode blank = UnoRuntime.queryInterface(XBlankNode.class, i_node);
+ return blank != null;
+ }
+
+
+ static Statement[] toSeq(XEnumeration i_Enum) throws Exception
+ {
+ java.util.Collection<Statement> c = new java.util.ArrayList<Statement>();
+ while (i_Enum.hasMoreElements()) {
+ Statement s = (Statement) i_Enum.nextElement();
+ c.add(s);
+ }
+ // java sucks
+ Object[] arr = c.toArray();
+ Statement[] ret = new Statement[arr.length];
+ for (int i = 0; i < arr.length; ++i) {
+ ret[i] = (Statement) arr[i];
+ }
+ return ret;
+ }
+
+ static XNode[][] toSeqs(XEnumeration i_Enum) throws Exception
+ {
+ java.util.Collection<XNode[]> c = new java.util.ArrayList<XNode[]>();
+ while (i_Enum.hasMoreElements()) {
+ XNode[] s = (XNode[]) i_Enum.nextElement();
+ c.add(s);
+ }
+ Object[] arr = c.toArray();
+ XNode[][] ret = new XNode[arr.length][];
+ for (int i = 0; i < arr.length; ++i) {
+ ret[i] = (XNode[]) arr[i];
+ }
+ return ret;
+ }
+
+ private static class BindingComp implements java.util.Comparator<XNode[]>
+ {
+ public int compare(XNode[] left, XNode[] right)
+ {
+ if (left.length != right.length)
+ {
+ throw new RuntimeException();
+ }
+ for (int i = 0; i < left.length; ++i) {
+ int eq = (left[i].getStringValue().compareTo(
+ right[i].getStringValue()));
+ if (eq != 0)
+ {
+ return eq;
+ }
+ }
+ return 0;
+ }
+ }
+
+ private static class StmtComp implements java.util.Comparator<Statement>
+ {
+ public int compare(Statement left, Statement right)
+ {
+ int eq;
+ if ((eq = cmp(left.Graph, right.Graph )) != 0) return eq;
+ if ((eq = cmp(left.Subject, right.Subject )) != 0) return eq;
+ if ((eq = cmp(left.Predicate, right.Predicate)) != 0) return eq;
+ if ((eq = cmp(left.Object, right.Object )) != 0) return eq;
+ return 0;
+ }
+
+ private int cmp(XNode i_Left, XNode i_Right)
+ {
+ if (isBlank(i_Left)) {
+ return isBlank(i_Right) ? 0 : 1;
+ } else {
+ if (isBlank(i_Right)) {
+ return -1;
+ } else {
+ return toS(i_Left).compareTo(toS(i_Right));
+ }
+ }
+ }
+ }
+
+ static boolean eq(Statement i_Left, Statement i_Right)
+ {
+ XURI lG = i_Left.Graph;
+ XURI rG = i_Right.Graph;
+ if (!eq(lG, rG)) {
+ System.out.println("Graphs differ: " + toS(lG) + " != " + toS(rG));
+ return false;
+ }
+ if (!eq(i_Left.Subject, i_Right.Subject)) {
+ System.out.println("Subjects differ: " +
+ i_Left.Subject.getStringValue() + " != " +
+ i_Right.Subject.getStringValue());
+ return false;
+ }
+ if (!eq(i_Left.Predicate, i_Right.Predicate)) {
+ System.out.println("Predicates differ: " +
+ i_Left.Predicate.getStringValue() + " != " +
+ i_Right.Predicate.getStringValue());
+ return false;
+ }
+ if (!eq(i_Left.Object, i_Right.Object)) {
+ System.out.println("Objects differ: " +
+ i_Left.Object.getStringValue() + " != " +
+ i_Right.Object.getStringValue());
+ return false;
+ }
+ return true;
+ }
+
+ static boolean eq(Statement[] i_Result, Statement[] i_Expected)
+ {
+ if (i_Result.length != i_Expected.length) {
+ System.out.println("eq: different lengths: " + i_Result.length + " " +
+ i_Expected.length);
+ return false;
+ }
+ Statement[] expected = i_Expected.clone();
+ java.util.Arrays.sort(i_Result, new StmtComp());
+ java.util.Arrays.sort(expected, new StmtComp());
+ for (int i = 0; i < expected.length; ++i)
+ {
+ // This is better for debug!
+ final Statement a = i_Result[i];
+ final Statement b = expected[i];
+ final boolean cond = eq(a, b);
+ if (!cond) return false;
+ }
+ return true;
+ }
+
+ static boolean eq(XEnumeration i_Enum, Statement[] i_Expected)
+ throws Exception
+ {
+ Statement[] current = toSeq(i_Enum);
+ return eq(current, i_Expected);
+ }
+
+ static boolean eq(XNode i_Left, XNode i_Right)
+ {
+ if (i_Left == null) {
+ return (i_Right == null);
+ } else {
+ return (i_Right != null) &&
+ (i_Left.getStringValue().equals(i_Right.getStringValue())
+ // FIXME: hack: blank nodes considered equal
+ || (isBlank(i_Left) && isBlank(i_Right)));
+ }
+ }
+
+ static boolean eq(XQuerySelectResult i_Result,
+ String[] i_Vars, XNode[][] i_Bindings) throws Exception
+ {
+ String[] vars = i_Result.getBindingNames();
+ XEnumeration iter = i_Result;
+ XNode[][] bindings = toSeqs(iter);
+ if (vars.length != i_Vars.length) {
+ System.out.println("var lengths differ");
+ return false;
+ }
+ if (bindings.length != i_Bindings.length) {
+ System.out.println("binding lengths differ: " + i_Bindings.length +
+ " vs " + bindings.length );
+ return false;
+ }
+ java.util.Arrays.sort(bindings, new BindingComp());
+ java.util.Arrays.sort(i_Bindings, new BindingComp());
+ for (int i = 0; i < i_Bindings.length; ++i) {
+ if (i_Bindings[i].length != i_Vars.length) {
+ System.out.println("TEST ERROR!");
+ throw new Exception();
+ }
+ if (bindings[i].length != i_Vars.length) {
+ System.out.println("binding length and var length differ");
+ return false;
+ }
+ for (int j = 0; j < i_Vars.length; ++j) {
+ if (!eq(bindings[i][j], i_Bindings[i][j])) {
+ System.out.println("bindings differ: " +
+ toS(bindings[i][j]) + " != " + toS(i_Bindings[i][j]));
+ return false;
+ }
+ }
+ }
+ for (int i = 0; i < i_Vars.length; ++i) {
+ if (!vars[i].equals(i_Vars[i])) {
+ System.out.println("variable names differ: " +
+ vars[i] + " != " + i_Vars[i]);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ static boolean eq(StringPair i_Left, StringPair i_Right)
+ {
+ return ((i_Left.First).equals(i_Right.First)) &&
+ ((i_Left.Second).equals(i_Right.Second));
+ }
+
+ static String mkNamespace(String i_prefix, String i_namespace)
+ {
+ return "PREFIX " + i_prefix + ": <" + i_namespace + ">\n";
+ }
+
+ static String mkNss()
+ {
+ String namespaces = mkNamespace("rdf",
+ "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
+ namespaces += mkNamespace("pkg",
+ "http://docs.oasis-open.org/opendocument/meta/package/common#");
+ namespaces += mkNamespace("odf",
+ "http://docs.oasis-open.org/opendocument/meta/package/odf#");
+ return namespaces;
+ }
+
+ Statement[] getManifestStmts(XURI xBaseURI) throws Exception
+ {
+ XURI xManifest = URI.createNS(xContext, xBaseURI.getStringValue(),
+ manifestPath);
+ XURI xContent = URI.createNS(xContext, xBaseURI.getStringValue(),
+ contentPath);
+ XURI xStyles = URI.createNS(xContext, xBaseURI.getStringValue(),
+ stylesPath);
+ Statement xM_BaseTypeDoc =
+ new Statement(xBaseURI, rdf_type, pkg_Document, xManifest);
+ Statement xM_BaseHaspartContent =
+ new Statement(xBaseURI, pkg_hasPart, xContent, xManifest);
+ Statement xM_BaseHaspartStyles =
+ new Statement(xBaseURI, pkg_hasPart, xStyles, xManifest);
+ Statement xM_ContentTypeContent =
+ new Statement(xContent, rdf_type, odf_ContentFile, xManifest);
+ Statement xM_StylesTypeStyles =
+ new Statement(xStyles, rdf_type, odf_StylesFile, xManifest);
+ return new Statement[] {
+ xM_BaseTypeDoc, xM_BaseHaspartContent, xM_BaseHaspartStyles,
+ xM_ContentTypeContent, xM_StylesTypeStyles
+ };
+ }
+
+ Statement[] getMetadataFileStmts(XURI xBaseURI, String Path)
+ throws Exception
+ {
+ XURI xManifest = URI.createNS(xContext, xBaseURI.getStringValue(),
+ manifestPath);
+ XURI xGraph = URI.createNS(xContext, xBaseURI.getStringValue(), Path);
+ Statement xM_BaseHaspartGraph =
+ new Statement(xBaseURI, pkg_hasPart, xGraph, xManifest);
+ Statement xM_GraphTypeMetadata =
+ new Statement(xGraph, rdf_type, pkg_MetadataFile, xManifest);
+ return new Statement[] { xM_BaseHaspartGraph, xM_GraphTypeMetadata };
+ }
+
+ class TestRange implements XTextRange, XMetadatable, XServiceInfo
+ {
+ String m_Stream;
+ String m_XmlId;
+ String m_Text;
+ TestRange(String i_Str) { m_Text = i_Str; }
+
+ public String getStringValue() { return ""; }
+ public String getNamespace() { return ""; }
+ public String getLocalName() { return ""; }
+
+ public StringPair getMetadataReference()
+ {
+ return new StringPair(m_Stream, m_XmlId);
+ }
+ public void setMetadataReference(StringPair i_Ref)
+ throws IllegalArgumentException
+ {
+ m_Stream = i_Ref.First;
+ m_XmlId = i_Ref.Second;
+ }
+ public void ensureMetadataReference()
+ {
+ m_Stream = "content.xml";
+ m_XmlId = "42";
+ }
+
+ public String getImplementationName() { return null; }
+ public String[] getSupportedServiceNames() { return null; }
+ public boolean supportsService(String i_Svc)
+ {
+ return i_Svc.equals("com.sun.star.text.Paragraph");
+ }
+
+ public XText getText() { return null; }
+ public XTextRange getStart() { return null; }
+ public XTextRange getEnd() { return null; }
+ public String getString() { return m_Text; }
+ public void setString(String i_Str) { m_Text = i_Str; }
+ }
+
+
+
+ private XMultiServiceFactory getMSF()
+ {
+ return UnoRuntime.queryInterface(XMultiServiceFactory.class, connection.getComponentContext().getServiceManager());
+ }
+
+ // setup and close connections
+ @BeforeClass public static void setUpConnection() throws Exception {
+ System.out.println( "------------------------------------------------------------" );
+ System.out.println( "starting class: " + DocumentMetadataAccess.class.getName() );
+ System.out.println( "------------------------------------------------------------" );
+ connection.setUp();
+ }
+
+ @AfterClass public static void tearDownConnection()
+ throws InterruptedException, com.sun.star.uno.Exception
+ {
+ System.out.println( "------------------------------------------------------------" );
+ System.out.println( "finishing class: " + DocumentMetadataAccess.class.getName() );
+ System.out.println( "------------------------------------------------------------" );
+ connection.tearDown();
+ }
+
+ private static final OfficeConnection connection = new OfficeConnection();
+
+}
+
diff --git a/sfx2/qa/complex/sfx2/DocumentProperties.java b/sfx2/qa/complex/sfx2/DocumentProperties.java
new file mode 100644
index 0000000000..fe830f118d
--- /dev/null
+++ b/sfx2/qa/complex/sfx2/DocumentProperties.java
@@ -0,0 +1,516 @@
+/*
+ * 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 .
+ */
+
+package complex.sfx2;
+
+
+import complex.sfx2.tools.TestDocument;
+import com.sun.star.uno.UnoRuntime;
+import com.sun.star.uno.XComponentContext;
+import com.sun.star.lang.XInitialization;
+
+import com.sun.star.lang.XMultiServiceFactory;
+import com.sun.star.lang.Locale;
+import com.sun.star.lang.EventObject;
+import com.sun.star.util.Date;
+import com.sun.star.util.DateTime;
+import com.sun.star.util.Time;
+import com.sun.star.util.Duration;
+import com.sun.star.util.XModifyListener;
+import com.sun.star.util.XModifyBroadcaster;
+import com.sun.star.beans.XPropertyContainer;
+import com.sun.star.beans.XPropertySet;
+import com.sun.star.beans.PropertyValue;
+import com.sun.star.beans.NamedValue;
+import com.sun.star.beans.PropertyAttribute;
+import com.sun.star.beans.UnknownPropertyException;
+import com.sun.star.beans.IllegalTypeException;
+
+import com.sun.star.document.XDocumentProperties;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.openoffice.test.OfficeConnection;
+import static org.junit.Assert.*;
+
+/**
+ * Test case for the service com.sun.star.document.DocumentProperties.
+ * Currently, this service is implemented in
+ * sfx2/source/doc/SfxDocumentMetaData.cxx.
+ *
+ */
+public class DocumentProperties
+{
+ @After public void cleanup() {
+ // nothing to do
+ }
+
+ // for testing modifications
+ private class Listener implements XModifyListener {
+ private boolean m_Called;
+
+ Listener() {
+ m_Called = false;
+ }
+
+ private boolean reset() {
+ boolean oldCalled = m_Called;
+ m_Called = false;
+ return oldCalled;
+ }
+
+ public void modified(EventObject e) {
+ m_Called = true;
+ }
+
+ public void disposing(EventObject e) {
+ }
+ }
+
+ @Test public void check() throws Exception
+ {
+ XMultiServiceFactory xMSF = getMSF();
+ assertNotNull("could not create MultiServiceFactory.", xMSF);
+ XPropertySet xPropertySet = UnoRuntime.queryInterface(XPropertySet.class, xMSF);
+ Object defaultCtx = xPropertySet.getPropertyValue("DefaultContext");
+ XComponentContext xContext = UnoRuntime.queryInterface(XComponentContext.class, defaultCtx);
+ assertNotNull("could not get component context.", xContext);
+
+ // TODO: Path to temp
+ String temp = util.utils.getOfficeTemp/*Dir*/(xMSF);
+ System.out.println("tempdir: " + temp);
+
+ PropertyValue[] noArgs = { };
+ PropertyValue mimetype = new PropertyValue();
+ mimetype.Name = "MediaType";
+ mimetype.Value = "application/vnd.oasis.opendocument.text";
+ PropertyValue[] mimeArgs = { mimetype };
+ PropertyValue cfile = new PropertyValue();
+ cfile.Name = "URL";
+ cfile.Value = temp + "EMPTY.odt";
+ PropertyValue[] mimeEmptyArgs = { mimetype, cfile };
+
+ System.out.println("Creating service DocumentProperties...");
+
+ Object oDP =
+ xMSF.createInstance("com.sun.star.document.DocumentProperties");
+ XDocumentProperties xDP = UnoRuntime.queryInterface(XDocumentProperties.class, oDP);
+
+ System.out.println("...done");
+
+
+ System.out.println("Checking initialize ...");
+
+ XDocumentProperties xDP2 = UnoRuntime.queryInterface(XDocumentProperties.class, xMSF.createInstance("com.sun.star.document.DocumentProperties"));
+ XInitialization xInit = UnoRuntime.queryInterface(XInitialization.class, xDP2);
+ xInit.initialize(new Object[] { });
+
+ System.out.println("...done");
+
+ System.out.println("Checking storing default-initialized meta data ...");
+
+ xDP2.storeToMedium("", mimeEmptyArgs);
+
+ System.out.println("...done");
+
+ System.out.println("Checking loading default-initialized meta data ...");
+
+ xDP2.loadFromMedium("", mimeEmptyArgs);
+ assertEquals("Author", "", xDP2.getAuthor());
+
+ System.out.println("...done");
+
+ System.out.println("Checking loading from test document...");
+
+ String file = TestDocument.getUrl("TEST.odt");
+ xDP.loadFromMedium(file, noArgs);
+/* XInputStream xStream =
+ new StreamSimulator("./testdocuments/TEST.odt", true, param);
+ Object oSF =
+ xMSF.createInstance("com.sun.star.embed.StorageFactory");
+ XSingleServiceFactory xSF = (XSingleServiceFactory)
+ UnoRuntime.queryInterface(XSingleServiceFactory.class, oSF);
+ Object oStor = xSF.createInstanceWithArguments(
+ new Object[] { xStream });
+ XStorage xStor = (XStorage) UnoRuntime.queryInterface(
+ XStorage.class, oStor);
+ xDP.loadFromStorage(xStor);*/
+
+ System.out.println("...done");
+
+ System.out.println("Checking meta-data import...");
+
+ assertEquals("Author", "Karl-Heinz Mustermann", xDP.getAuthor());
+ assertEquals(
+ "Generator",
+ "StarOffice/8$Solaris_x86 OpenOffice.org_project/680m232$Build-9227",
+ xDP.getGenerator());
+ assertEquals("CreationDate", 2007, xDP.getCreationDate().Year);
+ assertEquals("Title", "Urgent Memo", xDP.getTitle());
+ assertEquals("Subject", "Wichtige Mitteilung", xDP.getSubject());
+ assertEquals(
+ "Description",
+ "Modern internal company memorandum in full-blocked style",
+ xDP.getDescription());
+ assertEquals(
+ "ModifiedBy", "Karl-Heinz Mustermann", xDP.getModifiedBy());
+ assertEquals(
+ "ModificationDate", 10, xDP.getModificationDate().Month);
+ assertEquals(
+ "PrintedBy", "Karl-Heinz Mustermann", xDP.getPrintedBy());
+ assertEquals("PrintDate", 29, xDP.getPrintDate().Day);
+ assertEquals("TemplateName", "Modern Memo", xDP.getTemplateName());
+ assertTrue("TemplateURL",
+ xDP.getTemplateURL().endsWith("memmodern.ott"));
+ assertEquals("TemplateDate", 17, xDP.getTemplateDate().Hours);
+ assertTrue(
+ "AutoloadURL", xDP.getAutoloadURL().endsWith("/TEST.odt"));
+ assertEquals("AutoloadSecs", 0, xDP.getAutoloadSecs());
+ assertEquals("DefaultTarget", "_blank", xDP.getDefaultTarget());
+ assertEquals("EditingCycles", 3, xDP.getEditingCycles());
+ assertEquals("EditingDuration", 320, xDP.getEditingDuration());
+
+ String[] kws = xDP.getKeywords();
+ assertTrue("Keywords", fromArray(kws).containsAll(
+ fromArray(new Object[] { "Asien", "Memo", "Reis" })));
+
+ NamedValue[] ds = xDP.getDocumentStatistics();
+ assertTrue("DocumentStatistics:WordCount", containsNV(ds,
+ new NamedValue("WordCount", Integer.valueOf(23))));
+ assertTrue("DocumentStatistics:PageCount", containsNV(ds,
+ new NamedValue("PageCount", Integer.valueOf(1))));
+
+ XPropertyContainer udpc = xDP.getUserDefinedProperties();
+ XPropertySet udps = UnoRuntime.queryInterface( XPropertySet.class, udpc );
+ assertEquals(
+ "UserDefined 1", "Dies ist ein wichtiger Hinweis",
+ udps.getPropertyValue("Hinweis"));
+ assertEquals(
+ "UserDefined 2", "Kann Spuren von N\u00FCssen enthalten",
+ udps.getPropertyValue("Warnung"));
+
+ System.out.println("...done");
+
+ System.out.println("Checking meta-data updates...");
+
+ String str;
+ DateTime dt = new DateTime();
+ Locale l = new Locale();
+ int i;
+
+ str = "me";
+ xDP.setAuthor(str);
+ assertEquals("setAuthor", str, xDP.getAuthor());
+ str = "the computa";
+ xDP.setGenerator(str);
+ assertEquals("setGenerator", str, xDP.getGenerator());
+ dt.Year = 2038;
+ dt.Month = 1;
+ dt.Day = 1;
+ xDP.setCreationDate(dt);
+ assertEquals(
+ "setCreationDate", dt.Year, xDP.getCreationDate().Year);
+ str = "El t'itulo";
+ xDP.setTitle(str);
+ assertEquals("setTitle", str, xDP.getTitle());
+ str = "Ein verkommenes Subjekt";
+ xDP.setSubject(str);
+ assertEquals("setSubject", str, xDP.getSubject());
+ str = "Este descripci'on no es importante";
+ xDP.setDescription(str);
+ assertEquals("setDescription", str, xDP.getDescription());
+ l.Language = "en";
+ l.Country = "GB";
+ xDP.setLanguage(l);
+ Locale l2 = xDP.getLanguage();
+ assertEquals("setLanguage Lang", l.Language, l2.Language);
+ assertEquals("setLanguage Cty", l.Country, l2.Country);
+ str = "myself";
+ xDP.setModifiedBy(str);
+ assertEquals("setModifiedBy", str, xDP.getModifiedBy());
+ dt.Year = 2042;
+ xDP.setModificationDate(dt);
+ assertEquals(
+ "setModificationDate", dt.Year, xDP.getModificationDate().Year);
+ str = "i did not do it";
+ xDP.setPrintedBy(str);
+ assertEquals("setPrintedBy", str, xDP.getPrintedBy());
+ dt.Year = 2024;
+ xDP.setPrintDate(dt);
+ assertEquals("setPrintDate", dt.Year, xDP.getPrintDate().Year);
+ str = "blah";
+ xDP.setTemplateName(str);
+ assertEquals("setTemplateName", str, xDP.getTemplateName());
+ str = "gopher://some-hole-in-the-ground/";
+ xDP.setTemplateURL(str);
+ assertEquals("setTemplateURL", str, xDP.getTemplateURL());
+ dt.Year = 2043;
+ xDP.setTemplateDate(dt);
+ assertEquals(
+ "setTemplateDate", dt.Year, xDP.getTemplateDate().Year);
+ str = "http://nowhere/";
+ xDP.setAutoloadURL(str);
+ assertEquals("setAutoloadURL", str, xDP.getAutoloadURL());
+ i = 3661; // this might not work (due to conversion via double...)
+ xDP.setAutoloadSecs(i);
+ assertEquals("setAutoloadSecs", i, xDP.getAutoloadSecs());
+ str = "_blank";
+ xDP.setDefaultTarget(str);
+ assertEquals("setDefaultTarget", str, xDP.getDefaultTarget());
+ i = 42;
+ xDP.setEditingCycles((short) i);
+ assertEquals("setEditingCycles", i, xDP.getEditingCycles());
+ i = 84;
+ xDP.setEditingDuration(i);
+ assertEquals("setEditingDuration", i, xDP.getEditingDuration());
+ str = "";
+
+ String[] kws2 = new String[] {
+ "keywordly", "keywordlike", "keywordalicious" };
+ xDP.setKeywords(kws2);
+ kws = xDP.getKeywords();
+ assertTrue("setKeywords", fromArray(kws).containsAll(fromArray(kws2)));
+
+ NamedValue[] ds2 = new NamedValue[] {
+ new NamedValue("SyllableCount", Integer.valueOf(9)),
+ new NamedValue("FrameCount", Integer.valueOf(2)),
+ new NamedValue("SentenceCount", Integer.valueOf(7)) };
+ xDP.setDocumentStatistics(ds2);
+ ds = xDP.getDocumentStatistics();
+ assertTrue("setDocumentStatistics:SyllableCount", containsNV(ds,
+ new NamedValue("SyllableCount", Integer.valueOf(9))));
+ assertTrue("setDocumentStatistics:FrameCount", containsNV(ds,
+ new NamedValue("FrameCount", Integer.valueOf(2))));
+ assertTrue("setDocumentStatistics:SentenceCount", containsNV(ds,
+ new NamedValue("SentenceCount", Integer.valueOf(7))));
+
+ System.out.println("...done");
+
+ System.out.println("Checking user-defined meta-data updates...");
+
+ // actually, this tests the PropertyBag service
+ // but maybe the DocumentProperties service will be implemented
+ // differently some day...
+ boolean b = true;
+ double d = 3.1415;
+ // note that Time is only supported for backward compatibility!
+ Time t = new Time();
+ t.Hours = 1;
+ t.Minutes = 16;
+ Date date = new Date();
+ date.Year = 2071;
+ date.Month = 2;
+ date.Day = 3;
+ dt.Year = 2065;
+ Duration dur = new Duration();
+ dur.Negative = true;
+ dur.Years = 1001;
+ dur.Months = 999;
+ dur.Days = 888;
+ dur.Hours = 777;
+ dur.Minutes = 666;
+ dur.Seconds = 555;
+ dur.NanoSeconds = 444444444;
+
+ udpc.addProperty("Frobnicate", PropertyAttribute.REMOVABLE, b);
+ udpc.addProperty("FrobDuration", PropertyAttribute.REMOVABLE, dur);
+ udpc.addProperty("FrobDuration2", PropertyAttribute.REMOVABLE, t);
+ udpc.addProperty("FrobEndDate", PropertyAttribute.REMOVABLE, date);
+ udpc.addProperty("FrobStartTime", PropertyAttribute.REMOVABLE, dt);
+ udpc.addProperty("Pi", PropertyAttribute.REMOVABLE, Double.valueOf(d));
+ udpc.addProperty("Foo", PropertyAttribute.REMOVABLE, "bar");
+ udpc.addProperty("Removed", PropertyAttribute.REMOVABLE, "bar");
+ // #i94175#: empty property name is valid ODF 1.1
+ udpc.addProperty("", PropertyAttribute.REMOVABLE, "eeeeek");
+ try {
+ udpc.removeProperty("Info 1");
+ udpc.removeProperty("Removed");
+ } catch (UnknownPropertyException e) {
+ fail("removeProperty failed");
+ }
+
+ try {
+ udpc.addProperty("Forbidden", PropertyAttribute.REMOVABLE,
+ new String[] { "foo", "bar" });
+ fail("inserting value of non-supported type did not fail");
+ } catch (IllegalTypeException e) {
+ // ignore
+ }
+
+ assertEquals(
+ "UserDefined bool", b, udps.getPropertyValue("Frobnicate"));
+ assertTrue("UserDefined duration", eqDuration(dur, (Duration)
+ udps.getPropertyValue("FrobDuration")));
+ assertTrue("UserDefined time", eqTime(t, (Time)
+ udps.getPropertyValue("FrobDuration2")));
+ assertTrue("UserDefined date", eqDate(date, (Date)
+ udps.getPropertyValue("FrobEndDate")));
+ assertTrue("UserDefined datetime", eqDateTime(dt, (DateTime)
+ udps.getPropertyValue("FrobStartTime")));
+ assertEquals("UserDefined float", d, udps.getPropertyValue("Pi"));
+ assertEquals(
+ "UserDefined string", "bar", udps.getPropertyValue("Foo"));
+ assertEquals(
+ "UserDefined empty name", "eeeeek", udps.getPropertyValue(""));
+
+ try {
+ udps.getPropertyValue("Removed");
+ fail("UserDefined remove didn't");
+ } catch (UnknownPropertyException e) {
+ // ok
+ }
+
+ System.out.println("...done");
+
+ System.out.println("Checking storing meta-data to file...");
+
+ xDP.storeToMedium(temp + "TEST.odt", mimeArgs);
+
+ System.out.println("...done");
+
+ System.out.println("Checking loading meta-data from stored file...");
+
+ xDP.loadFromMedium(temp + "TEST.odt", noArgs);
+
+ System.out.println("...done");
+
+ System.out.println("Checking user-defined meta-data from stored file...");
+
+ udpc = xDP.getUserDefinedProperties();
+ udps = UnoRuntime.queryInterface( XPropertySet.class, udpc );
+
+ assertEquals(
+ "UserDefined bool", b, udps.getPropertyValue("Frobnicate"));
+ assertTrue("UserDefined duration", eqDuration(dur, (Duration)
+ udps.getPropertyValue("FrobDuration")));
+ // this is now a Duration!
+ Duration t_dur = new Duration(false, (short)0, (short)0, (short)0,
+ t.Hours, t.Minutes, t.Seconds,
+ t.NanoSeconds);
+ assertTrue("UserDefined time", eqDuration(t_dur, (Duration)
+ udps.getPropertyValue("FrobDuration2")));
+ assertTrue("UserDefined date", eqDate(date, (Date)
+ udps.getPropertyValue("FrobEndDate")));
+ assertTrue("UserDefined datetime", eqDateTime(dt, (DateTime)
+ udps.getPropertyValue("FrobStartTime")));
+ assertEquals("UserDefined float", d, udps.getPropertyValue("Pi"));
+ assertEquals(
+ "UserDefined string", "bar", udps.getPropertyValue("Foo"));
+
+ try {
+ udps.getPropertyValue("Removed");
+ fail("UserDefined remove didn't");
+ } catch (UnknownPropertyException e) {
+ // ok
+ }
+
+ System.out.println("...done");
+
+ System.out.println("Checking notification listener interface...");
+
+ Listener listener = new Listener();
+ XModifyBroadcaster xMB = UnoRuntime.queryInterface( XModifyBroadcaster.class, xDP );
+ xMB.addModifyListener(listener);
+ xDP.setAuthor("not me");
+ assertTrue("Listener Author", listener.reset());
+ udpc.addProperty("Listener", PropertyAttribute.REMOVABLE, "foo");
+ assertTrue("Listener UserDefined Add", listener.reset());
+ udps.setPropertyValue("Listener", "bar");
+ assertTrue("Listener UserDefined Set", listener.reset());
+ udpc.removeProperty("Listener");
+ assertTrue("Listener UserDefined Remove", listener.reset());
+ xMB.removeModifyListener(listener);
+ udpc.addProperty("Listener2", PropertyAttribute.REMOVABLE, "foo");
+ assertTrue("Removed Listener UserDefined Add", !listener.reset());
+
+ System.out.println("...done");
+ }
+
+ // grrr...
+ boolean eqDateTime(DateTime a, DateTime b) {
+ return a.Year == b.Year && a.Month == b.Month && a.Day == b.Day
+ && a.Hours == b.Hours && a.Minutes == b.Minutes
+ && a.Seconds == b.Seconds
+ && a.NanoSeconds == b.NanoSeconds;
+ }
+
+ boolean eqDate(Date a, Date b) {
+ return a.Year == b.Year && a.Month == b.Month && a.Day == b.Day;
+ }
+
+ boolean eqTime(Time a, Time b) {
+ return a.Hours == b.Hours && a.Minutes == b.Minutes
+ && a.Seconds == b.Seconds
+ && a.NanoSeconds == b.NanoSeconds;
+ }
+
+ boolean eqDuration(Duration a, Duration b) {
+ return a.Years == b.Years && a.Months == b.Months && a.Days == b.Days
+ && a.Hours == b.Hours && a.Minutes == b.Minutes
+ && a.Seconds == b.Seconds
+ && a.NanoSeconds == b.NanoSeconds
+ && a.Negative == b.Negative;
+ }
+
+ java.util.Collection<Object> fromArray(Object[] os) {
+ java.util.Collection<Object> ret = new java.util.HashSet<Object>();
+ for (int i = 0; i < os.length; ++i) {
+ ret.add(os[i]);
+ }
+ return ret;
+ }
+
+ // bah, structs do not have proper equals(), and uno.Type is not comparable
+ public static boolean containsNV (NamedValue[] nvs, NamedValue nv) {
+ for (int i = 0; i < nvs.length; ++i) {
+ if (nvs[i].Name.equals(nv.Name) && nvs[i].Value.equals(nv.Value)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private XMultiServiceFactory getMSF()
+ {
+ final XMultiServiceFactory xMSF1 = UnoRuntime.queryInterface( XMultiServiceFactory.class, connection.getComponentContext().getServiceManager() );
+ return xMSF1;
+ }
+
+ // setup and close connections
+ @BeforeClass public static void setUpConnection() throws Exception {
+ System.out.println( "------------------------------------------------------------" );
+ System.out.println( "starting class: " + DocumentProperties.class.getName() );
+ System.out.println( "------------------------------------------------------------" );
+ connection.setUp();
+ }
+
+ @AfterClass public static void tearDownConnection()
+ throws InterruptedException, com.sun.star.uno.Exception
+ {
+ System.out.println( "------------------------------------------------------------" );
+ System.out.println( "finishing class: " + DocumentProperties.class.getName() );
+ System.out.println( "------------------------------------------------------------" );
+ connection.tearDown();
+ }
+
+ private static final OfficeConnection connection = new OfficeConnection();
+
+}
+
diff --git a/sfx2/qa/complex/sfx2/GlobalEventBroadcaster.java b/sfx2/qa/complex/sfx2/GlobalEventBroadcaster.java
new file mode 100644
index 0000000000..7c42cdf1f3
--- /dev/null
+++ b/sfx2/qa/complex/sfx2/GlobalEventBroadcaster.java
@@ -0,0 +1,249 @@
+/*
+ * 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 .
+ */
+package complex.sfx2;
+
+import com.sun.star.awt.XWindow;
+import com.sun.star.document.XEventBroadcaster;
+import com.sun.star.document.XEventListener;
+import com.sun.star.lang.XMultiServiceFactory;
+import com.sun.star.sheet.XSpreadsheetDocument;
+import com.sun.star.text.XTextDocument;
+import com.sun.star.uno.UnoRuntime;
+import complex.sfx2.tools.WriterHelper;
+
+import java.util.ArrayList;
+
+import util.UITools;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.openoffice.test.OfficeConnection;
+import static org.junit.Assert.*;
+
+
+/**
+ * This testcase checks the GlobalEventBroadcaster
+ * it will add an XEventListener and verify the Events
+ * raised when opening/changing and closing Office Documents
+ */
+public class GlobalEventBroadcaster {
+ XMultiServiceFactory m_xMSF = null;
+ XEventBroadcaster m_xEventBroadcaster = null;
+ ArrayList<String> notifyEvents = new ArrayList<String>();
+ // XTextDocument xTextDoc;
+ XSpreadsheetDocument xSheetDoc;
+ XEventListener m_xEventListener = new EventListenerImpl();
+
+ @Before public void initialize() {
+ m_xMSF = getMSF();
+ System.out.println("check whether there is a valid MultiServiceFactory");
+
+ assertNotNull("## Couldn't get MultiServiceFactory make sure your Office is started", m_xMSF);
+
+ System.out.println("... done");
+
+ System.out.println(
+ "Create an instance of com.sun.star.frame.GlobalEventBroadcaster");
+
+ Object GlobalEventBroadcaster = null;
+
+ try {
+ GlobalEventBroadcaster = m_xMSF.createInstance(
+ "com.sun.star.frame.GlobalEventBroadcaster");
+ } catch (com.sun.star.uno.Exception e) {
+ fail("## Exception while creating instance");
+ }
+
+ System.out.println("... done");
+
+ System.out.println("check whether the created instance is valid");
+
+ assertNotNull("couldn't create service", GlobalEventBroadcaster);
+
+ System.out.println("... done");
+
+ System.out.println(
+ "try to query the XEventBroadcaster from the gained Object");
+ m_xEventBroadcaster = UnoRuntime.queryInterface(XEventBroadcaster.class, GlobalEventBroadcaster);
+
+ if (util.utils.isVoid(m_xEventBroadcaster)) {
+ fail("couldn't get XEventBroadcaster");
+ }
+
+ System.out.println("... done");
+
+ System.out.println("adding Listener");
+ m_xEventBroadcaster.addEventListener(m_xEventListener);
+ System.out.println("... done");
+ }
+
+ @Test public void checkWriter() throws Exception {
+ System.out.println("-- Checking Writer --");
+
+ WriterHelper wHelper = new WriterHelper(m_xMSF);
+ String[] expected;
+ System.out.println("opening an empty writer doc");
+ notifyEvents.clear();
+ {
+ XTextDocument xTextDoc = wHelper.openEmptyDoc();
+ util.utils.waitForEventIdle(m_xMSF);
+ expected = new String[] { "OnUnfocus", "OnCreate", "OnViewCreated", "OnFocus" };
+
+ assertTrue("Wrong events fired when opening empty doc",
+ proveExpectation(expected));
+ System.out.println("... done");
+
+ System.out.println("changing the writer doc");
+ notifyEvents.clear();
+ xTextDoc.getText().setString("GlobalEventBroadcaster");
+ util.utils.waitForEventIdle(m_xMSF);
+ expected = new String[] { "OnModifyChanged" };
+
+ assertTrue("Wrong events fired when changing doc",
+ proveExpectation(expected));
+ System.out.println("... done");
+
+ System.out.println("closing the empty writer doc");
+ notifyEvents.clear();
+ wHelper.closeDoc(xTextDoc);
+ util.utils.waitForEventIdle(m_xMSF);
+ }
+ expected = new String[] { "OnUnfocus", "OnFocus", "OnViewClosed", "OnUnload" };
+
+ assertTrue("Wrong events fired when closing empty doc",
+ proveExpectation(expected));
+ System.out.println("... done");
+
+ System.out.println("opening a writer doc via Window-New Window");
+ notifyEvents.clear();
+ {
+ XTextDocument xTextDoc = wHelper.openFromDialog(".uno:NewWindow", "", false);
+
+ util.utils.waitForEventIdle(m_xMSF);
+ expected = new String[] { "OnUnfocus", "OnCreate", "OnViewCreated", "OnFocus", "OnUnfocus", "OnViewCreated", "OnFocus", };
+
+ assertTrue("Wrong events fired when opening a writer doc via Window-New Window",
+ proveExpectation(expected));
+ System.out.println("... done");
+
+ System.out.println("closing the created writer doc");
+ notifyEvents.clear();
+
+ wHelper.closeDoc(xTextDoc);
+ util.utils.waitForEventIdle(m_xMSF);
+ }
+ expected = new String[] { "OnViewClosed", "OnUnfocus", "OnFocus", "OnViewClosed", "OnUnload" };
+
+ assertTrue("Wrong events fired when closing Window-New Window",
+ proveExpectation(expected));
+
+ System.out.println("... done");
+ // TODO: It seems not possible to close the document without interactive question
+ // there the follow test will not be execute
+ if (false) {
+ System.out.println("Opening document with label wizard");
+ XTextDocument xTextDoc = wHelper.openFromDialog("private:factory/swriter?slot=21051", "", false);
+ util.utils.waitForEventIdle(m_xMSF);
+ XWindow xWindow = UnoRuntime.queryInterface(XWindow.class, wHelper.getToolkit().getActiveTopWindow());
+ UITools ut = new UITools(xWindow);
+ notifyEvents.clear();
+ System.out.println("pressing button 'New Document'");
+ try{
+ ut.clickButton ("New Document");
+ } catch (Exception e) {
+ System.out.println("Couldn't press Button");
+ }
+ System.out.println("... done");
+ util.utils.waitForEventIdle(m_xMSF);
+ expected = new String[] { "OnViewClosed", "OnCreate", "OnFocus", "OnModifyChanged" };
+
+ assertTrue("Wrong events fired when starting labels wizard",
+ proveExpectation(expected));
+
+ System.out.println("Try to close document...");
+ wHelper.closeDoc(xTextDoc);
+ util.utils.waitForEventIdle(m_xMSF);
+ wHelper.closeFromDialog();
+ util.utils.waitForEventIdle(m_xMSF);
+ xTextDoc = null;
+ }
+
+ System.out.println("-- Done Writer --");
+ }
+
+ @After public void cleanup() {
+ System.out.println("removing Listener");
+ m_xEventBroadcaster.removeEventListener(m_xEventListener);
+ System.out.println("... done");
+ }
+
+ private boolean proveExpectation(String[] expected) {
+ boolean locRes = true;
+ boolean failure = false;
+
+ System.out.println("Fired Events:");
+ for (int k=0;k<notifyEvents.size();k++) {
+ System.out.println("\t- "+notifyEvents.get(k));
+ }
+
+ for (int i = 0; i < expected.length; i++) {
+ locRes = notifyEvents.contains(expected[i]);
+
+ if (!locRes) {
+ System.out.println("The event " + expected[i] + " isn't fired");
+ failure = true;
+ }
+ }
+
+ return !failure;
+ }
+
+ private class EventListenerImpl implements XEventListener {
+ public void disposing(com.sun.star.lang.EventObject eventObject) {
+ System.out.println("disposing: " + eventObject.Source.toString());
+ }
+
+ public void notifyEvent(com.sun.star.document.EventObject eventObject) {
+ notifyEvents.add(eventObject.EventName);
+ }
+ }
+
+ private XMultiServiceFactory getMSF()
+ {
+ return UnoRuntime.queryInterface(XMultiServiceFactory.class, connection.getComponentContext().getServiceManager());
+ }
+
+ // setup and close connections
+ @BeforeClass public static void setUpConnection() throws Exception {
+ System.out.println("setUpConnection()");
+ connection.setUp();
+ }
+
+ @AfterClass public static void tearDownConnection()
+ throws InterruptedException, com.sun.star.uno.Exception
+ {
+ System.out.println("tearDownConnection() CheckGlobalEventBroadcaster_writer1");
+ connection.tearDown();
+ }
+
+ private static final OfficeConnection connection = new OfficeConnection();
+
+}
diff --git a/sfx2/qa/complex/sfx2/UndoManager.java b/sfx2/qa/complex/sfx2/UndoManager.java
new file mode 100644
index 0000000000..ea1a349651
--- /dev/null
+++ b/sfx2/qa/complex/sfx2/UndoManager.java
@@ -0,0 +1,1460 @@
+/*
+ * 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 .
+ */
+
+package complex.sfx2;
+
+import com.sun.star.accessibility.XAccessible;
+import com.sun.star.accessibility.XAccessibleAction;
+import com.sun.star.awt.Point;
+import com.sun.star.awt.Size;
+import com.sun.star.awt.XControl;
+import com.sun.star.awt.XControlModel;
+import com.sun.star.awt.XToolkitExperimental;
+import com.sun.star.beans.NamedValue;
+import com.sun.star.beans.XPropertySet;
+import com.sun.star.container.NoSuchElementException;
+import com.sun.star.container.XChild;
+import com.sun.star.container.XIndexContainer;
+import com.sun.star.container.XNameContainer;
+import com.sun.star.container.XNameReplace;
+import com.sun.star.container.XSet;
+import com.sun.star.document.EmptyUndoStackException;
+import com.sun.star.document.UndoContextNotClosedException;
+import com.sun.star.document.UndoFailedException;
+import com.sun.star.document.UndoManagerEvent;
+import com.sun.star.document.XEmbeddedScripts;
+import com.sun.star.document.XEventsSupplier;
+import com.sun.star.document.XUndoAction;
+import com.sun.star.lang.EventObject;
+import com.sun.star.lang.IndexOutOfBoundsException;
+import com.sun.star.lang.XEventListener;
+
+import java.lang.reflect.InvocationTargetException;
+
+import org.openoffice.test.tools.OfficeDocument;
+
+import com.sun.star.document.XUndoManagerSupplier;
+import com.sun.star.document.XUndoManager;
+import com.sun.star.document.XUndoManagerListener;
+import com.sun.star.drawing.XControlShape;
+import com.sun.star.drawing.XDrawPage;
+import com.sun.star.drawing.XDrawPageSupplier;
+import com.sun.star.drawing.XShapes;
+import com.sun.star.lang.XComponent;
+import com.sun.star.lang.XMultiServiceFactory;
+import com.sun.star.lang.XServiceInfo;
+import com.sun.star.lang.XSingleComponentFactory;
+import com.sun.star.lang.XTypeProvider;
+import com.sun.star.script.ScriptEventDescriptor;
+import com.sun.star.script.XEventAttacherManager;
+import com.sun.star.script.XLibraryContainer;
+import com.sun.star.task.XJob;
+import com.sun.star.uno.Exception;
+import com.sun.star.uno.Type;
+import com.sun.star.uno.UnoRuntime;
+import com.sun.star.uno.XComponentContext;
+import com.sun.star.util.InvalidStateException;
+import com.sun.star.util.NotLockedException;
+import com.sun.star.view.XControlAccess;
+
+import complex.sfx2.undo.CalcDocumentTest;
+import complex.sfx2.undo.ChartDocumentTest;
+import complex.sfx2.undo.DocumentTest;
+import complex.sfx2.undo.DrawDocumentTest;
+import complex.sfx2.undo.ImpressDocumentTest;
+import complex.sfx2.undo.WriterDocumentTest;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Stack;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+import org.openoffice.test.OfficeConnection;
+import org.openoffice.test.tools.DocumentType;
+import org.openoffice.test.tools.SpreadsheetDocument;
+
+/**
+ * Unit test for the UndoManager API
+ *
+ */
+public class UndoManager
+{
+
+ @Before
+ public void beforeTest() throws com.sun.star.uno.Exception
+ {
+ m_currentTestCase = null;
+ m_currentDocument = null;
+ m_undoListener = null;
+
+ // at our service factory, insert a new factory for our CallbackComponent
+ // this will allow the Basic code in our test documents to call back into this test case
+ // here, by just instantiating this service
+ final XSet globalFactory = UnoRuntime.queryInterface( XSet.class, getORB() );
+ m_callbackFactory = new CallbackComponentFactory();
+ globalFactory.insert( m_callbackFactory );
+ }
+
+
+ @Test
+ public void checkWriterUndo() throws Exception
+ {
+ m_currentTestCase = new WriterDocumentTest( getORB() );
+ impl_checkUndo();
+ }
+
+
+//FIXME fails fdo#35663 @Test
+ public void checkCalcUndo() throws java.lang.Exception
+ {
+ m_currentTestCase = new CalcDocumentTest( getORB() );
+ impl_checkUndo();
+ }
+
+
+ @Test
+ public void checkDrawUndo() throws Exception
+ {
+ m_currentTestCase = new DrawDocumentTest( getORB() );
+ impl_checkUndo();
+ }
+
+
+ @Test
+ public void checkImpressUndo() throws Exception
+ {
+ m_currentTestCase = new ImpressDocumentTest( getORB() );
+ impl_checkUndo();
+ }
+
+
+ @Test
+ public void checkChartUndo() throws Exception
+ {
+ m_currentTestCase = new ChartDocumentTest( getORB() );
+ impl_checkUndo();
+ }
+
+
+ @Test
+ public void checkBrokenScripts() throws com.sun.star.uno.Exception, InterruptedException
+ {
+ System.out.println( "testing: broken scripts" );
+
+ m_currentDocument = OfficeDocument.blankDocument( getORB(), DocumentType.CALC );
+ m_undoListener = new UndoListener();
+ getUndoManager().addUndoManagerListener( m_undoListener );
+
+ impl_setupBrokenBasicScript();
+ final String scriptURI = "vnd.sun.star.script:default.callbacks.brokenScript?language=Basic&location=document";
+
+
+ // scenario 1: Pressing a button which is bound to execute the script
+ // (This is one of the many cases where SfxObjectShell::CallXScript is invoked)
+
+ // set up the button
+ final XPropertySet buttonModel = impl_setupButton();
+ buttonModel.setPropertyValue( "Label", "exec broken script" );
+ impl_assignScript( buttonModel, "XActionListener", "actionPerformed",
+ scriptURI );
+
+ // switch the doc's view to form alive mode (so the button will actually work)
+ m_currentDocument.getCurrentView().dispatch( ".uno:SwitchControlDesignMode" );
+ XToolkitExperimental xToolkit = UnoRuntime.queryInterface(
+ XToolkitExperimental.class,
+ getORB().createInstance("com.sun.star.awt.Toolkit"));
+ xToolkit.processEventsToIdle();
+
+ // click the button
+ m_callbackCalled = false;
+ impl_clickButton( buttonModel );
+ // the macro is executed asynchronously by the button, so wait at most 2 seconds for the callback to be
+ // triggered
+ impl_waitFor( m_callbackCondition, 20000 );
+ // check the callback has actually been called
+ assertTrue( "clicking the test button did not work as expected - basic script not called", m_callbackCalled );
+
+ // again, since the script is executed asynchronously, we might arrive here while its execution
+ // is not completely finished. Give OOo another (at most) 2 seconds to finish it.
+ m_undoListener.waitForAllContextsClosed( 20000 );
+ // assure that the Undo Context Depth of the doc is still "0": The Basic script entered such a
+ // context, and didn't close it (thus it is broken), but the application framework should have
+ // auto-closed the context after the macro finished.
+ assertEquals( "undo context was not auto-closed as expected", 0, m_undoListener.getCurrentUndoContextDepth() );
+
+
+ // scenario 2: dispatching the script URL. Technically, this is equivalent to configuring the
+ // script into a menu or toolbar, and selecting the respective menu/toolbar item
+ m_callbackCalled = false;
+ m_currentDocument.getCurrentView().dispatch( scriptURI );
+ assertTrue( "dispatching the Script URL did not work as expected - basic script not called", m_callbackCalled );
+ // same as above: The script didn't close the context, but the OOo framework should have
+ assertEquals( "undo context was not auto-closed as expected", 0, m_undoListener.getCurrentUndoContextDepth() );
+
+
+ // scenario 3: assigning the script to some document event, and triggering this event
+ final XEventsSupplier eventSupplier = UnoRuntime.queryInterface( XEventsSupplier.class, m_currentDocument.getDocument() );
+ final XNameReplace events = UnoRuntime.queryInterface( XNameReplace.class, eventSupplier.getEvents() );
+ final NamedValue[] scriptDescriptor = new NamedValue[] {
+ new NamedValue( "EventType", "Script" ),
+ new NamedValue( "Script", scriptURI )
+ };
+ events.replaceByName( "OnViewCreated", scriptDescriptor );
+
+ // note: this may be prevented from working by setting
+ // Office::Common::Security::Scripting::AllowedDocumentEventURLs
+ // (checked in SfxEvents_Impl::isScriptURLAllowed())
+ m_callbackCalled = false;
+ m_currentDocument.getCurrentView().dispatch( ".uno:NewWindow" );
+ assertTrue( "triggering an event did not work as expected - basic script not called", m_callbackCalled );
+ // same as above: The script didn't close the context, but the OOo framework should have
+ assertEquals( "undo context was not auto-closed as expected", 0, m_undoListener.getCurrentUndoContextDepth() );
+
+
+ // scenario 4: let the script enter an Undo context, but not close it, as usual.
+ // Additionally, let the script close the document - the OOo framework code which cares for
+ // auto-closing of Undo contexts should survive this, ideally ...
+ m_closeAfterCallback = true;
+ m_callbackCalled = false;
+ m_currentDocument.getCurrentView().dispatch( scriptURI );
+ assertTrue( m_callbackCalled );
+ assertTrue( "The Basic script should have closed the document.", m_undoListener.isDisposed() );
+ m_currentDocument = null;
+ }
+
+
+ @Test
+ public void checkSerialization() throws com.sun.star.uno.Exception, InterruptedException
+ {
+ System.out.println( "testing: request serialization" );
+
+ m_currentDocument = OfficeDocument.blankDocument( getORB(), DocumentType.CALC );
+ final XUndoManager undoManager = getUndoManager();
+
+ final int threadCount = 10;
+ final int actionsPerThread = 10;
+ final int actionCount = threadCount * actionsPerThread;
+
+ // add some actions to the UndoManager, each knowing its position on the stack
+ final Object lock = new Object();
+ final Integer actionsUndone[] = new Integer[] { 0 };
+ for ( int i=actionCount; i>0; )
+ undoManager.addUndoAction( new CountingUndoAction( --i, lock, actionsUndone ) );
+
+ // some concurrent threads which undo the actions
+ Thread[] threads = new Thread[threadCount];
+ for ( int i=0; i<threadCount; ++i )
+ {
+ threads[i] = new Thread()
+ {
+ @Override
+ public void run()
+ {
+ for ( int j=0; j<actionsPerThread; ++j )
+ {
+ try { undoManager.undo(); }
+ catch ( final Exception e )
+ {
+ fail( "Those dummy actions are not expected to fail." );
+ return;
+ }
+ }
+ }
+ };
+ }
+
+ // start the threads
+ for ( int i=0; i<threadCount; ++i )
+ threads[i].start();
+
+ // wait for them to be finished
+ for ( int i=0; i<threadCount; ++i )
+ threads[i].join();
+
+ // ensure all actions have been undone
+ assertEquals( "not all actions have been undone", actionCount, actionsUndone[0].intValue() );
+ }
+
+
+ @After
+ public void afterTest()
+ {
+ if ( m_currentTestCase != null )
+ m_currentTestCase.closeDocument();
+ else if ( m_currentDocument != null )
+ m_currentDocument.close();
+ m_currentTestCase = null;
+ m_currentDocument = null;
+ m_callbackFactory.dispose();
+ }
+
+
+ /**
+ * @return returns the undo manager belonging to a given document
+ */
+ private XUndoManager getUndoManager()
+ {
+ final XUndoManagerSupplier suppUndo = UnoRuntime.queryInterface( XUndoManagerSupplier.class, m_currentDocument.getDocument() );
+ final XUndoManager undoManager = suppUndo.getUndoManager();
+ assertTrue( UnoRuntime.areSame( undoManager.getParent(), m_currentDocument.getDocument() ) );
+ return undoManager;
+ }
+
+
+ private void impl_waitFor( final Object i_condition, final int i_milliSeconds ) throws InterruptedException
+ {
+ synchronized( i_condition )
+ {
+ i_condition.wait( i_milliSeconds );
+ }
+ }
+
+
+ private void impl_setupBrokenBasicScript()
+ {
+ try
+ {
+ final XEmbeddedScripts embeddedScripts = UnoRuntime.queryInterface( XEmbeddedScripts.class, m_currentDocument.getDocument() );
+ final XLibraryContainer basicLibs = embeddedScripts.getBasicLibraries();
+ final XNameContainer basicLib = basicLibs.createLibrary( "default" );
+
+ final String brokenScriptCode =
+ "Option Explicit\n" +
+ "\n" +
+ "Sub brokenScript\n" +
+ " Dim callback as Object\n" +
+ " ThisComponent.UndoManager.enterUndoContext( \"" + getCallbackUndoContextTitle() + "\" )\n" +
+ "\n" +
+ " callback = createUnoService( \"" + getCallbackComponentServiceName() + "\" )\n" +
+ " Dim emptyArgs() as new com.sun.star.beans.NamedValue\n" +
+ " Dim result as String\n" +
+ " result = callback.execute( emptyArgs() )\n" +
+ " If result = \"close\" Then\n" +
+ " ThisComponent.close( TRUE )\n" +
+ " End If\n" +
+ "End Sub\n" +
+ "\n";
+
+ basicLib.insertByName( "callbacks", brokenScriptCode );
+ }
+ catch( com.sun.star.uno.Exception e )
+ {
+ fail( "caught an exception while setting up the script: " + e.toString() );
+ }
+ }
+
+
+ private XPropertySet impl_setupButton() throws com.sun.star.uno.Exception
+ {
+ // let the document create a shape
+ final XMultiServiceFactory docAsFactory = UnoRuntime.queryInterface( XMultiServiceFactory.class,
+ m_currentDocument.getDocument() );
+ final XControlShape xShape = UnoRuntime.queryInterface( XControlShape.class,
+ docAsFactory.createInstance( "com.sun.star.drawing.ControlShape" ) );
+
+ // position and size of the shape
+ xShape.setSize( new Size( 28 * 100, 10 * 100 ) );
+ xShape.setPosition( new Point( 10 * 100, 10 * 100 ) );
+
+ // create the form component (the model of a form control)
+ final String sQualifiedComponentName = "com.sun.star.form.component.CommandButton";
+ final XControlModel controlModel = UnoRuntime.queryInterface( XControlModel.class,
+ getORB().createInstance( sQualifiedComponentName ) );
+
+ // knitt both
+ xShape.setControl( controlModel );
+
+ // add the shape to the shapes collection of the document
+ SpreadsheetDocument spreadsheetDoc = (SpreadsheetDocument)m_currentDocument;
+ final XDrawPageSupplier suppDrawPage = UnoRuntime.queryInterface( XDrawPageSupplier.class,
+ spreadsheetDoc.getSheet( 0 ) );
+ final XDrawPage insertIntoPage = suppDrawPage.getDrawPage();
+
+ final XShapes sheetShapes = UnoRuntime.queryInterface( XShapes.class, insertIntoPage );
+ sheetShapes.add( xShape );
+
+ return UnoRuntime.queryInterface( XPropertySet.class, controlModel );
+ }
+
+
+ private void impl_assignScript( final XPropertySet i_controlModel, final String i_interfaceName,
+ final String i_interfaceMethod, final String i_scriptURI ) throws Exception
+ {
+ final XChild modelAsChild = UnoRuntime.queryInterface( XChild.class, i_controlModel );
+ final XIndexContainer parentForm = UnoRuntime.queryInterface( XIndexContainer.class, modelAsChild.getParent() );
+
+ final XEventAttacherManager manager = UnoRuntime.queryInterface( XEventAttacherManager.class, parentForm );
+
+ int containerPosition = -1;
+ for ( int i = 0; i < parentForm.getCount(); ++i )
+ {
+ final XPropertySet child = UnoRuntime.queryInterface( XPropertySet.class, parentForm.getByIndex( i ) );
+ if ( UnoRuntime.areSame( child, i_controlModel ) )
+ {
+ containerPosition = i;
+ break;
+ }
+ }
+ assertFalse( "could not find the given control model within its parent", containerPosition == -1 );
+ manager.registerScriptEvent( containerPosition, new ScriptEventDescriptor(
+ i_interfaceName,
+ i_interfaceMethod,
+ "",
+ "Script",
+ i_scriptURI
+ ) );
+ }
+
+
+ private void impl_clickButton( final XPropertySet i_buttonModel ) throws NoSuchElementException, IndexOutOfBoundsException
+ {
+ final XControlAccess controlAccess = UnoRuntime.queryInterface( XControlAccess.class,
+ m_currentDocument.getCurrentView().getController() );
+ final XControl control = controlAccess.getControl( UnoRuntime.queryInterface( XControlModel.class, i_buttonModel ) );
+ final XAccessible accessible = UnoRuntime.queryInterface( XAccessible.class, control );
+ final XAccessibleAction controlActions = UnoRuntime.queryInterface( XAccessibleAction.class, accessible.getAccessibleContext() );
+ for ( int i=0; i<controlActions.getAccessibleActionCount(); ++i )
+ {
+ if (controlActions.getAccessibleActionDescription(i).equals("press"))
+ {
+ controlActions.doAccessibleAction(i);
+ return;
+ }
+ }
+ fail("did not find the accessible action named 'press'");
+ }
+
+
+ private static class UndoListener implements XUndoManagerListener
+ {
+ public void undoActionAdded( UndoManagerEvent i_event )
+ {
+ assertFalse( "|undoActionAdded| called after document was disposed", m_isDisposed );
+
+ ++m_undoActionsAdded;
+ m_mostRecentlyAddedAction = i_event.UndoActionTitle;
+ }
+
+ public void actionUndone( UndoManagerEvent i_event )
+ {
+ assertFalse( "|actionUndone| called after document was disposed", m_isDisposed );
+
+ ++m_undoCount;
+ m_mostRecentlyUndone = i_event.UndoActionTitle;
+ }
+
+ public void actionRedone( UndoManagerEvent i_event )
+ {
+ assertFalse( "|actionRedone| called after document was disposed", m_isDisposed );
+
+ ++m_redoCount;
+ }
+
+ public void allActionsCleared( EventObject eo )
+ {
+ assertFalse( "|allActionsCleared| called after document was disposed", m_isDisposed );
+
+ m_wasCleared = true;
+ }
+
+ public void redoActionsCleared( EventObject eo )
+ {
+ assertFalse( "|redoActionsCleared| called after document was disposed", m_isDisposed );
+
+ m_redoWasCleared = true;
+ }
+
+ public void resetAll( EventObject i_event )
+ {
+ assertFalse( "|resetAll| called after document was disposed", m_isDisposed );
+
+ m_managerWasReset = true;
+ m_activeUndoContexts.clear();
+ }
+
+ public void enteredContext( UndoManagerEvent i_event )
+ {
+ assertFalse( "|enteredContext| called after document was disposed", m_isDisposed );
+
+ m_activeUndoContexts.push( i_event.UndoActionTitle );
+ assertEquals( "different opinions on the context nesting level (after entering)",
+ m_activeUndoContexts.size(), i_event.UndoContextDepth );
+ }
+
+ public void enteredHiddenContext( UndoManagerEvent i_event )
+ {
+ assertFalse( "|enteredHiddenContext| called after document was disposed", m_isDisposed );
+
+ m_activeUndoContexts.push( i_event.UndoActionTitle );
+ assertEquals( "different opinions on the context nesting level (after entering hidden)",
+ m_activeUndoContexts.size(), i_event.UndoContextDepth );
+ }
+
+ public void leftContext( UndoManagerEvent i_event )
+ {
+ assertFalse( "|leftContext| called after document was disposed", m_isDisposed );
+
+ assertEquals( "nested undo context descriptions do not match", m_activeUndoContexts.pop(), i_event.UndoActionTitle );
+ assertEquals( "different opinions on the context nesting level (after leaving)",
+ m_activeUndoContexts.size(), i_event.UndoContextDepth );
+ m_leftContext = true;
+ impl_notifyContextDepth();
+ }
+
+ public void leftHiddenContext( UndoManagerEvent i_event )
+ {
+ assertFalse( "|leftHiddenContext| called after document was disposed", m_isDisposed );
+ assertEquals( "|leftHiddenContext| is not expected to notify an action title", 0, i_event.UndoActionTitle.length() );
+
+ m_activeUndoContexts.pop();
+ assertEquals( "different opinions on the context nesting level (after leaving)",
+ m_activeUndoContexts.size(), i_event.UndoContextDepth );
+ m_leftHiddenContext = true;
+ impl_notifyContextDepth();
+ }
+
+ public void cancelledContext( UndoManagerEvent i_event )
+ {
+ assertFalse( "|cancelledContext| called after document was disposed", m_isDisposed );
+ assertEquals( "|cancelledContext| is not expected to notify an action title", 0, i_event.UndoActionTitle.length() );
+
+ m_activeUndoContexts.pop();
+ assertEquals( "different opinions on the context nesting level (after cancelling)",
+ m_activeUndoContexts.size(), i_event.UndoContextDepth );
+ m_cancelledContext = true;
+ impl_notifyContextDepth();
+ }
+
+ public void disposing( EventObject i_event )
+ {
+ m_isDisposed = true;
+ }
+
+ public void waitForAllContextsClosed( final int i_milliSeconds ) throws InterruptedException
+ {
+ synchronized ( m_allContextsClosedCondition )
+ {
+ if ( m_activeUndoContexts.empty() )
+ return;
+ m_allContextsClosedCondition.wait( i_milliSeconds );
+ }
+ }
+
+ private void impl_notifyContextDepth()
+ {
+ synchronized ( m_allContextsClosedCondition )
+ {
+ if ( m_activeUndoContexts.empty() )
+ {
+ m_allContextsClosedCondition.notifyAll();
+ }
+ }
+ }
+
+ private int getUndoActionsAdded() { return m_undoActionsAdded; }
+ private int getUndoActionCount() { return m_undoCount; }
+ private int getRedoActionCount() { return m_redoCount; }
+ private String getCurrentUndoContextTitle() { return m_activeUndoContexts.peek(); }
+ private String getMostRecentlyAddedActionTitle() { return m_mostRecentlyAddedAction; }
+ private String getMostRecentlyUndoneTitle() { return m_mostRecentlyUndone; }
+ private int getCurrentUndoContextDepth() { return m_activeUndoContexts.size(); }
+ private boolean isDisposed() { return m_isDisposed; }
+ private boolean wasContextLeft() { return m_leftContext; }
+ private boolean wasHiddenContextLeft() { return m_leftHiddenContext; }
+ private boolean hasContextBeenCancelled() { return m_cancelledContext; }
+ private boolean wereStacksCleared() { return m_wasCleared; }
+ private boolean wasRedoStackCleared() { return m_redoWasCleared; }
+ private boolean wasManagerReset() { return m_managerWasReset; }
+
+ void reset()
+ {
+ m_undoActionsAdded = m_undoCount = m_redoCount = 0;
+ m_activeUndoContexts.clear();
+ m_mostRecentlyAddedAction = m_mostRecentlyUndone = null;
+ // m_isDisposed is not cleared, intentionally
+ m_leftContext = m_leftHiddenContext = m_cancelledContext = m_wasCleared = m_redoWasCleared = m_managerWasReset = false;
+ }
+
+ private int m_undoActionsAdded = 0;
+ private int m_undoCount = 0;
+ private int m_redoCount = 0;
+ private boolean m_isDisposed = false;
+ private boolean m_leftContext = false;
+ private boolean m_leftHiddenContext = false;
+ private boolean m_cancelledContext = false;
+ private boolean m_wasCleared = false;
+ private boolean m_redoWasCleared = false;
+ private boolean m_managerWasReset = false;
+ private Stack< String >
+ m_activeUndoContexts = new Stack<String>();
+ private String m_mostRecentlyAddedAction = null;
+ private String m_mostRecentlyUndone = null;
+ private final Object m_allContextsClosedCondition = new Object();
+ }
+
+
+ private void impl_checkUndo() throws Exception
+ {
+ System.out.println( "testing: " + m_currentTestCase.getDocumentDescription() );
+ m_currentDocument = m_currentTestCase.getDocument();
+ m_currentTestCase.initializeDocument();
+ m_currentTestCase.verifyInitialDocumentState();
+
+ final XUndoManager undoManager = getUndoManager();
+ undoManager.clear();
+ assertFalse( "clearing the Undo manager should result in the impossibility to undo anything", undoManager.isUndoPossible() );
+ assertFalse( "clearing the Undo manager should result in the impossibility to redo anything", undoManager.isRedoPossible() );
+
+ m_undoListener = new UndoListener();
+ undoManager.addUndoManagerListener( m_undoListener );
+
+ impl_testSingleModification( undoManager );
+ impl_testMultipleModifications( undoManager );
+ impl_testCustomUndoActions( undoManager );
+ impl_testLocking( undoManager );
+ impl_testNestedContexts( undoManager );
+ impl_testErrorHandling( undoManager );
+ impl_testContextHandling( undoManager );
+ impl_testStackHandling( undoManager );
+ impl_testClearance( undoManager );
+ impl_testHiddenContexts( undoManager );
+
+ // close the document, ensure the Undo manager listener gets notified
+ m_currentTestCase.closeDocument();
+ m_currentTestCase = null;
+ m_currentDocument = null;
+ assertTrue( "document is closed, but the UndoManagerListener has not been notified of the disposal", m_undoListener.isDisposed() );
+ }
+
+
+ private void impl_testSingleModification( final XUndoManager i_undoManager ) throws com.sun.star.uno.Exception
+ {
+ m_currentTestCase.doSingleModification();
+ m_currentTestCase.verifySingleModificationDocumentState();
+
+ // undo the modification, ensure the listener got the proper notifications
+ assertEquals( "We did not yet do an undo!", 0, m_undoListener.getUndoActionCount() );
+ i_undoManager.undo();
+ assertEquals( "A simple undo does not result in the proper Undo count.",
+ 1, m_undoListener.getUndoActionCount() );
+
+ // verify the document is in its initial state, again
+ m_currentTestCase.verifyInitialDocumentState();
+
+ // redo the modification, ensure the listener got the proper notifications
+ assertEquals( "not yet made a redo!", 0, m_undoListener.getRedoActionCount() );
+ i_undoManager.redo();
+ assertEquals( "made a redo, but got no notification of it!", 1, m_undoListener.getRedoActionCount() );
+ // ensure the document is in the proper state, again
+ m_currentTestCase.verifySingleModificationDocumentState();
+
+ // now do an Undo via the UI (aka the dispatch API), and see if this works, and notifies the listener as
+ // expected
+ m_currentTestCase.getDocument().getCurrentView().dispatch( ".uno:Undo" );
+ m_currentTestCase.verifyInitialDocumentState();
+ assertEquals( "UI-Undo does not notify the listener", 2, m_undoListener.getUndoActionCount() );
+ }
+
+
+ private void impl_testMultipleModifications( final XUndoManager i_undoManager ) throws com.sun.star.uno.Exception
+ {
+ m_undoListener.reset();
+ assertEquals( "unexpected initial undo context depth", 0, m_undoListener.getCurrentUndoContextDepth() );
+ i_undoManager.enterUndoContext( "Batch Changes" );
+ assertEquals( "unexpected undo context depth after entering a context",
+ 1, m_undoListener.getCurrentUndoContextDepth() );
+ assertEquals( "entering an Undo context has not been notified properly",
+ "Batch Changes", m_undoListener.getCurrentUndoContextTitle() );
+
+ final int modifications = m_currentTestCase.doMultipleModifications();
+ assertEquals( "unexpected number of undo actions while doing batch changes to the document",
+ modifications, m_undoListener.getUndoActionsAdded() );
+ assertEquals( "seems the document operations touched the undo context depth",
+ 1, m_undoListener.getCurrentUndoContextDepth() );
+
+ i_undoManager.leaveUndoContext();
+ assertEquals( "unexpected undo context depth after leaving the last context",
+ 0, m_undoListener.getCurrentUndoContextDepth() );
+ assertEquals( "no Undo done, yet - still the listener has been notified of an Undo action",
+ 0, m_undoListener.getUndoActionCount() );
+
+ i_undoManager.undo();
+ assertEquals( "Just did an undo - the listener should have been notified", 1, m_undoListener.getUndoActionCount() );
+ m_currentTestCase.verifyInitialDocumentState();
+ }
+
+
+ private void impl_testCustomUndoActions( final XUndoManager i_undoManager ) throws com.sun.star.uno.Exception
+ {
+ i_undoManager.clear();
+ m_undoListener.reset();
+ assertFalse( "undo stack not empty after clearing the undo manager", i_undoManager.isUndoPossible() );
+ assertFalse( "redo stack not empty after clearing the undo manager", i_undoManager.isRedoPossible() );
+ assertArrayEquals( ">0 descriptions for an empty undo stack?",
+ new String[0], i_undoManager.getAllUndoActionTitles() );
+ assertArrayEquals( ">0 descriptions for an empty redo stack?",
+ new String[0], i_undoManager.getAllRedoActionTitles() );
+
+ // add two actions, one directly, one within a context
+ final CustomUndoAction action1 = new CustomUndoAction( "UndoAction1" );
+ i_undoManager.addUndoAction( action1 );
+ assertEquals( "Adding an undo action not observed by the listener", 1, m_undoListener.getUndoActionsAdded() );
+ assertEquals( "Adding an undo action did not notify the proper title",
+ action1.getTitle(), m_undoListener.getMostRecentlyAddedActionTitle() );
+ final String contextTitle = "Undo Context";
+ i_undoManager.enterUndoContext( contextTitle );
+ final CustomUndoAction action2 = new CustomUndoAction( "UndoAction2" );
+ i_undoManager.addUndoAction( action2 );
+ assertEquals( "Adding an undo action not observed by the listener",
+ 2, m_undoListener.getUndoActionsAdded() );
+ assertEquals( "Adding an undo action did not notify the proper title",
+ action2.getTitle(), m_undoListener.getMostRecentlyAddedActionTitle() );
+ i_undoManager.leaveUndoContext();
+
+ // see if the manager has proper descriptions
+ assertArrayEquals( "unexpected Redo descriptions after adding two actions",
+ new String[0], i_undoManager.getAllRedoActionTitles() );
+ assertArrayEquals( "unexpected Undo descriptions after adding two actions",
+ new String[]{contextTitle, action1.getTitle()}, i_undoManager.getAllUndoActionTitles() );
+
+ // undo one action
+ i_undoManager.undo();
+ assertEquals( "improper action title notified during programmatic Undo",
+ contextTitle, m_undoListener.getMostRecentlyUndoneTitle() );
+ assertTrue( "nested custom undo action has not been undone as expected", action2.undoCalled() );
+ assertFalse( "nested custom undo action has not been undone as expected", action1.undoCalled() );
+ assertArrayEquals( "unexpected Redo descriptions after undoing a nested custom action",
+ new String[]{contextTitle}, i_undoManager.getAllRedoActionTitles() );
+ assertArrayEquals( "unexpected Undo descriptions after undoing a nested custom action",
+ new String[]{action1.getTitle()}, i_undoManager.getAllUndoActionTitles() );
+
+ // undo the second action, via UI dispatches
+ m_currentTestCase.getDocument().getCurrentView().dispatch( ".uno:Undo" );
+ assertEquals( "improper action title notified during UI Undo", action1.getTitle(), m_undoListener.getMostRecentlyUndoneTitle() );
+ assertTrue( "nested custom undo action has not been undone as expected", action1.undoCalled() );
+ assertArrayEquals( "unexpected Redo descriptions after undoing the second custom action",
+ new String[]{action1.getTitle(), contextTitle}, i_undoManager.getAllRedoActionTitles() );
+ assertArrayEquals( "unexpected Undo descriptions after undoing the second custom action",
+ new String[0], i_undoManager.getAllUndoActionTitles() );
+
+ // check the actions are disposed when the stacks are cleared
+ i_undoManager.clear();
+ assertTrue( action1.disposed() && action2.disposed() );
+ }
+
+
+ private void impl_testLocking( final XUndoManager i_undoManager ) throws com.sun.star.uno.Exception
+ {
+ i_undoManager.reset();
+ m_undoListener.reset();
+
+ // implicit Undo actions, triggered by changes to the document
+ assertFalse( "unexpected initial locking state", i_undoManager.isLocked() );
+ i_undoManager.lock();
+ assertTrue( "just locked the manager, why does it lie?", i_undoManager.isLocked() );
+ m_currentTestCase.doSingleModification();
+ assertEquals( "when the Undo manager is locked, no implicit additions should happen",
+ 0, m_undoListener.getUndoActionsAdded() );
+ assertTrue( "Undo manager gets unlocked as a side effect of performing a simple operation", i_undoManager.isLocked() );
+ i_undoManager.unlock();
+ assertEquals( "unlock is not expected to add collected actions - they should be discarded",
+ 0, m_undoListener.getUndoActionsAdded() );
+ assertFalse( "just unlocked the manager, why does it lie?", i_undoManager.isLocked() );
+
+ // explicit Undo actions
+ i_undoManager.lock();
+ i_undoManager.addUndoAction( new CustomUndoAction() );
+ i_undoManager.unlock();
+ assertEquals( "explicit Undo actions are expected to be ignored when the manager is locked",
+ 0, m_undoListener.getUndoActionsAdded() );
+
+ // Undo contexts while being locked
+ i_undoManager.lock();
+ i_undoManager.enterUndoContext( "Dummy Context" );
+ i_undoManager.enterHiddenUndoContext();
+ assertEquals( "entering Undo contexts should be ignored when the manager is locked", 0, m_undoListener.getCurrentUndoContextDepth() );
+ i_undoManager.leaveUndoContext();
+ i_undoManager.leaveUndoContext();
+ i_undoManager.unlock();
+
+ // |unlock| error handling
+ assertFalse( "internal error: manager should not be locked at this point in time", i_undoManager.isLocked() );
+ boolean caughtExpected = false;
+ try { i_undoManager.unlock(); } catch ( final NotLockedException e ) { caughtExpected = true; }
+ assertTrue( "unlocking the manager when it is not locked should throw", caughtExpected );
+ }
+
+
+ private void impl_testContextHandling( final XUndoManager i_undoManager ) throws com.sun.star.uno.Exception
+ {
+
+ // part I: non-empty contexts
+ i_undoManager.reset();
+ m_undoListener.reset();
+
+ // put one action on the undo and one on the redo stack, as precondition for the following tests
+ final XUndoAction undoAction1 = new CustomUndoAction( "Undo Action 1" );
+ i_undoManager.addUndoAction( undoAction1 );
+ final XUndoAction undoAction2 = new CustomUndoAction( "Undo Action 2" );
+ i_undoManager.addUndoAction( undoAction2 );
+ i_undoManager.undo();
+ assertTrue( "precondition for context handling tests not met (1)", i_undoManager.isUndoPossible() );
+ assertTrue( "precondition for context handling tests not met (2)", i_undoManager.isRedoPossible() );
+ assertArrayEquals( new String[] { undoAction1.getTitle() }, i_undoManager.getAllUndoActionTitles() );
+ assertArrayEquals( new String[] { undoAction2.getTitle() }, i_undoManager.getAllRedoActionTitles() );
+
+ final String[] expectedRedoActionComments = new String[] { undoAction2.getTitle() };
+ assertArrayEquals( expectedRedoActionComments, i_undoManager.getAllRedoActionTitles() );
+
+ // enter a context
+ i_undoManager.enterUndoContext( "Undo Context" );
+ // this should not (yet) touch the redo stack
+ assertArrayEquals( expectedRedoActionComments, i_undoManager.getAllRedoActionTitles() );
+ assertEquals( "unexpected undo context depth after entering a context", 1, m_undoListener.getCurrentUndoContextDepth() );
+ // add a single action
+ XUndoAction undoAction3 = new CustomUndoAction( "Undo Action 3" );
+ i_undoManager.addUndoAction( undoAction3 );
+ // still, the redo stack should be untouched - added at a lower level does not affect it at all
+ assertArrayEquals( expectedRedoActionComments, i_undoManager.getAllRedoActionTitles() );
+
+ // while the context is open, its title should already contribute to the stack, ...
+ assertEquals( "Undo Context", i_undoManager.getCurrentUndoActionTitle() );
+ // ... getAllUndo/RedoActionTitles should operate on the top level, not on the level defined by the open
+ // context, ...
+ assertArrayEquals( new String[] { "Undo Context", undoAction1.getTitle() },
+ i_undoManager.getAllUndoActionTitles() );
+ // ... but Undo and Redo should be impossible as long as the context is open
+ assertFalse( i_undoManager.isUndoPossible() );
+ assertFalse( i_undoManager.isRedoPossible() );
+
+ // leave the context, check the listener has been notified properly, and the notified context depth is correct
+ i_undoManager.leaveUndoContext();
+ assertTrue( m_undoListener.wasContextLeft() );
+ assertFalse( m_undoListener.wasHiddenContextLeft() );
+ assertFalse( m_undoListener.hasContextBeenCancelled() );
+ assertEquals( "unexpected undo context depth leaving a non-empty context", 0, m_undoListener.getCurrentUndoContextDepth() );
+ // leaving a non-empty context should have cleared the redo stack
+ assertArrayEquals( new String[0], i_undoManager.getAllRedoActionTitles() );
+ assertTrue( m_undoListener.wasRedoStackCleared() );
+
+
+ // part II: empty contexts
+ i_undoManager.reset();
+ m_undoListener.reset();
+
+ // enter a context, leave it immediately without adding an action to it
+ i_undoManager.enterUndoContext( "Undo Context" );
+ i_undoManager.leaveUndoContext();
+ assertFalse( m_undoListener.wasContextLeft() );
+ assertFalse( m_undoListener.wasHiddenContextLeft() );
+ assertTrue( m_undoListener.hasContextBeenCancelled() );
+ assertFalse( "leaving an empty context should silently remove it, and not contribute to the stack",
+ i_undoManager.isUndoPossible() );
+ }
+
+
+ private void impl_testNestedContexts( final XUndoManager i_undoManager ) throws com.sun.star.uno.Exception
+ {
+ i_undoManager.reset();
+ m_undoListener.reset();
+ i_undoManager.enterUndoContext( "context 1" );
+ i_undoManager.enterUndoContext( "context 1.1" );
+ final CustomUndoAction action1 = new CustomUndoAction( "action 1.1.1" );
+ i_undoManager.addUndoAction( action1 );
+ i_undoManager.enterUndoContext( "context 1.1.2" );
+ final CustomUndoAction action2 = new CustomUndoAction( "action 1.1.2.1" );
+ i_undoManager.addUndoAction( action2 );
+ i_undoManager.leaveUndoContext();
+ final CustomUndoAction action3 = new CustomUndoAction( "action 1.1.3" );
+ i_undoManager.addUndoAction( action3 );
+ i_undoManager.leaveUndoContext();
+ i_undoManager.leaveUndoContext();
+ final CustomUndoAction action4 = new CustomUndoAction( "action 1.2" );
+ i_undoManager.addUndoAction( action4 );
+
+ i_undoManager.undo();
+ assertEquals( "undoing a single action notifies a wrong title", action4.getTitle(), m_undoListener.getMostRecentlyUndoneTitle() );
+ assertTrue( "custom Undo not called", action4.undoCalled() );
+ assertFalse( "too many custom Undos called", action1.undoCalled() || action2.undoCalled() || action3.undoCalled() );
+ i_undoManager.undo();
+ assertTrue( "nested actions not properly undone", action1.undoCalled() && action2.undoCalled() && action3.undoCalled() );
+ }
+
+
+ private void impl_testErrorHandling( final XUndoManager i_undoManager ) throws com.sun.star.uno.Exception
+ {
+ i_undoManager.reset();
+ m_undoListener.reset();
+
+ // try retrieving the comments for the current Undo/Redo - this should fail
+ boolean caughtExpected = false;
+ try { i_undoManager.getCurrentUndoActionTitle(); }
+ catch( final EmptyUndoStackException e ) { caughtExpected = true; }
+ assertTrue( "trying the title of the current Undo action is expected to fail for an empty stack", caughtExpected );
+
+ caughtExpected = false;
+ try { i_undoManager.getCurrentRedoActionTitle(); }
+ catch( final EmptyUndoStackException e ) { caughtExpected = true; }
+ assertTrue( "trying the title of the current Redo action is expected to fail for an empty stack", caughtExpected );
+
+ caughtExpected = false;
+ try { i_undoManager.undo(); } catch ( final EmptyUndoStackException e ) { caughtExpected = true; }
+ assertTrue( "undo should throw if no Undo action is on the stack", caughtExpected );
+
+ caughtExpected = false;
+ try { i_undoManager.redo(); } catch ( final EmptyUndoStackException e ) { caughtExpected = true; }
+ assertTrue( "redo should throw if no Redo action is on the stack", caughtExpected );
+
+ caughtExpected = false;
+ try { i_undoManager.leaveUndoContext(); } catch ( final InvalidStateException e ) { caughtExpected = true; }
+ assertTrue( "leaveUndoContext should throw if no context is currently open", caughtExpected );
+
+ caughtExpected = false;
+ try { i_undoManager.addUndoAction( null ); } catch ( com.sun.star.lang.IllegalArgumentException e ) { caughtExpected = true; }
+ assertTrue( "adding a NULL action should be rejected", caughtExpected );
+
+ i_undoManager.reset();
+ i_undoManager.addUndoAction( new CustomUndoAction() );
+ i_undoManager.addUndoAction( new CustomUndoAction() );
+ i_undoManager.undo();
+ i_undoManager.enterUndoContext( "Undo Context" );
+ // those methods should fail when a context is open:
+ final String[] methodNames = new String[] { "undo", "redo", "clear", "clearRedo" };
+ for ( int i=0; i<methodNames.length; ++i )
+ {
+ caughtExpected = false;
+ try
+ {
+ Method method = i_undoManager.getClass().getMethod( methodNames[i], new Class[0] );
+ method.invoke( i_undoManager, new Object[0] );
+ }
+ catch ( IllegalAccessException ex ) { }
+ catch ( IllegalArgumentException ex ) { }
+ catch ( InvocationTargetException ex )
+ {
+ Throwable targetException = ex.getTargetException();
+ caughtExpected = ( targetException instanceof UndoContextNotClosedException );
+ }
+ catch ( NoSuchMethodException ex ) { }
+ catch ( SecurityException ex ) { }
+
+ assertTrue( methodNames[i] + " should be rejected when there is an open context", caughtExpected );
+ }
+ i_undoManager.leaveUndoContext();
+
+ // try Undo actions which fail in their Undo/Redo
+ for ( int i=0; i<4; ++i )
+ {
+ final boolean undo = ( i < 2 );
+ final boolean doByAPI = ( i % 2 ) == 0;
+
+ i_undoManager.reset();
+ i_undoManager.addUndoAction( new CustomUndoAction() );
+ i_undoManager.addUndoAction( new FailingUndoAction( undo ? FAIL_UNDO : FAIL_REDO ) );
+ i_undoManager.addUndoAction( new CustomUndoAction() );
+ i_undoManager.undo();
+ if ( !undo )
+ i_undoManager.undo();
+ // assert preconditions for the below test
+ assertTrue( i_undoManager.isUndoPossible() );
+ assertTrue( i_undoManager.isRedoPossible() );
+
+ boolean caughtUndoFailed = false;
+ try
+ {
+ if ( undo )
+ if ( doByAPI )
+ i_undoManager.undo();
+ else
+ m_currentTestCase.getDocument().getCurrentView().dispatch( ".uno:Undo" );
+ else
+ if ( doByAPI )
+ i_undoManager.redo();
+ else
+ m_currentTestCase.getDocument().getCurrentView().dispatch( ".uno:Redo" );
+ }
+ catch ( UndoFailedException e )
+ {
+ caughtUndoFailed = true;
+ }
+ if ( doByAPI )
+ assertTrue( "Exceptions in XUndoAction.undo should be propagated at the API", caughtUndoFailed );
+ else
+ assertFalse( "Undo/Redo by UI should not let escape Exceptions", caughtUndoFailed );
+ if ( undo )
+ {
+ assertFalse( "a failing Undo should clear the Undo stack", i_undoManager.isUndoPossible() );
+ assertTrue( "a failing Undo should /not/ clear the Redo stack", i_undoManager.isRedoPossible() );
+ }
+ else
+ {
+ assertTrue( "a failing Redo should /not/ clear the Undo stack", i_undoManager.isUndoPossible() );
+ assertFalse( "a failing Redo should clear the Redo stack", i_undoManager.isRedoPossible() );
+ }
+ }
+ }
+
+
+ private void impl_testStackHandling( final XUndoManager i_undoManager ) throws com.sun.star.uno.Exception
+ {
+ i_undoManager.reset();
+ m_undoListener.reset();
+
+ assertFalse( i_undoManager.isUndoPossible() );
+ assertFalse( i_undoManager.isRedoPossible() );
+
+ i_undoManager.addUndoAction( new CustomUndoAction() );
+ assertTrue( i_undoManager.isUndoPossible() );
+ assertFalse( i_undoManager.isRedoPossible() );
+ i_undoManager.addUndoAction( new CustomUndoAction() );
+ assertTrue( i_undoManager.isUndoPossible() );
+ assertFalse( i_undoManager.isRedoPossible() );
+ i_undoManager.undo();
+ assertTrue( i_undoManager.isUndoPossible() );
+ assertTrue( i_undoManager.isRedoPossible() );
+ i_undoManager.undo();
+ assertFalse( i_undoManager.isUndoPossible() );
+ assertTrue( i_undoManager.isRedoPossible() );
+ i_undoManager.addUndoAction( new CustomUndoAction() );
+ assertTrue( i_undoManager.isUndoPossible() );
+ assertFalse( "adding a new action should have cleared the Redo stack", i_undoManager.isRedoPossible() );
+ }
+
+
+ private void impl_testClearance( final XUndoManager i_undoManager ) throws com.sun.star.uno.Exception
+ {
+ i_undoManager.reset();
+ m_undoListener.reset();
+
+ // add an action, clear the stack, verify the listener has been called
+ i_undoManager.addUndoAction( new CustomUndoAction() );
+ assertFalse( "clearance listener unexpectedly called", m_undoListener.wereStacksCleared() );
+ assertFalse( "redo-clearance listener unexpectedly called", m_undoListener.wasRedoStackCleared() );
+ i_undoManager.clear();
+ assertTrue( "clearance listener not called as expected", m_undoListener.wereStacksCleared() );
+ assertFalse( "redo-clearance listener unexpectedly called (2)", m_undoListener.wasRedoStackCleared() );
+
+ // ensure the listener is also called if the stack is actually empty at the moment of the call
+ m_undoListener.reset();
+ assertFalse( i_undoManager.isUndoPossible() );
+ i_undoManager.clear();
+ assertTrue( "clearance listener is also expected to be called if the stack was empty before", m_undoListener.wereStacksCleared() );
+
+ // ensure the proper listeners are called for clearRedo
+ m_undoListener.reset();
+ i_undoManager.clearRedo();
+ assertFalse( m_undoListener.wereStacksCleared() );
+ assertTrue( m_undoListener.wasRedoStackCleared() );
+
+ // ensure the redo listener is also called upon implicit redo stack clearance
+ m_undoListener.reset();
+ i_undoManager.addUndoAction( new CustomUndoAction() );
+ i_undoManager.addUndoAction( new CustomUndoAction() );
+ i_undoManager.undo();
+ assertTrue( i_undoManager.isUndoPossible() );
+ assertTrue( i_undoManager.isRedoPossible() );
+ i_undoManager.addUndoAction( new CustomUndoAction() );
+ assertFalse( i_undoManager.isRedoPossible() );
+ assertTrue( "implicit clearance of the Redo stack does not notify listeners", m_undoListener.wasRedoStackCleared() );
+
+ // test resetting the manager
+ m_undoListener.reset();
+ i_undoManager.addUndoAction( new CustomUndoAction() );
+ i_undoManager.addUndoAction( new CustomUndoAction() );
+ i_undoManager.undo();
+ assertTrue( i_undoManager.isUndoPossible() );
+ assertTrue( i_undoManager.isRedoPossible() );
+ i_undoManager.reset();
+ assertFalse( i_undoManager.isUndoPossible() );
+ assertFalse( i_undoManager.isRedoPossible() );
+ assertTrue( "|reset| does not properly notify", m_undoListener.wasManagerReset() );
+
+ // resetting the manager, with open undo contexts
+ m_undoListener.reset();
+ i_undoManager.addUndoAction( new CustomUndoAction() );
+ i_undoManager.enterUndoContext( "Undo Context" );
+ i_undoManager.addUndoAction( new CustomUndoAction() );
+ i_undoManager.enterHiddenUndoContext();
+ i_undoManager.reset();
+ assertTrue( "|reset| while contexts are open does not properly notify", m_undoListener.wasManagerReset() );
+ // verify the manager really has the proper context depth now
+ i_undoManager.enterUndoContext( "Undo Context" );
+ assertEquals( "seems that |reset| did not really close the open contexts", 1, m_undoListener.getCurrentUndoContextDepth() );
+ }
+
+
+ private void impl_testHiddenContexts( final XUndoManager i_undoManager ) throws com.sun.star.uno.Exception
+ {
+ i_undoManager.reset();
+ m_undoListener.reset();
+ assertFalse( "precondition for testing hidden undo contexts not met", i_undoManager.isUndoPossible() );
+
+ // entering a hidden context should be rejected if the stack is empty
+ boolean caughtExpected = false;
+ try { i_undoManager.enterHiddenUndoContext(); }
+ catch ( final EmptyUndoStackException e ) { caughtExpected = true; }
+ assertTrue( "entering hidden contexts should be denied on an empty stack", caughtExpected );
+
+ // but it should be allowed if the context is not empty
+ final CustomUndoAction undoAction0 = new CustomUndoAction( "Step 0" );
+ i_undoManager.addUndoAction( undoAction0 );
+ final CustomUndoAction undoAction1 = new CustomUndoAction( "Step 1" );
+ i_undoManager.addUndoAction( undoAction1 );
+ i_undoManager.enterHiddenUndoContext();
+ final CustomUndoAction hiddenUndoAction = new CustomUndoAction( "hidden context action" );
+ i_undoManager.addUndoAction( hiddenUndoAction );
+ i_undoManager.leaveUndoContext();
+ assertFalse( "leaving a hidden should not call |leftUndocontext|", m_undoListener.wasContextLeft() );
+ assertTrue( "leaving a hidden does not call |leftHiddenUndocontext|", m_undoListener.wasHiddenContextLeft() );
+ assertFalse( "leaving a non-empty hidden context claims to have cancelled it", m_undoListener.hasContextBeenCancelled() );
+ assertEquals( "leaving a hidden context is not properly notified", 0, m_undoListener.getCurrentUndoContextDepth() );
+ assertArrayEquals( "unexpected Undo stack after leaving a hidden context",
+ new String[] { undoAction1.getTitle(), undoAction0.getTitle() },
+ i_undoManager.getAllUndoActionTitles() );
+
+ // and then calling |undo| once should not only undo everything in the hidden context, but also
+ // the previous action - but not more
+ i_undoManager.undo();
+ assertTrue( "Undo after leaving a hidden context does not actually undo the context actions",
+ hiddenUndoAction.undoCalled() );
+ assertTrue( "Undo after leaving a hidden context does not undo the predecessor action",
+ undoAction1.undoCalled() );
+ assertFalse( "Undo after leaving a hidden context undoes too much",
+ undoAction0.undoCalled() );
+
+ // leaving an empty hidden context should call the proper notification method
+ m_undoListener.reset();
+ i_undoManager.enterHiddenUndoContext();
+ i_undoManager.leaveUndoContext();
+ assertFalse( m_undoListener.wasContextLeft() );
+ assertFalse( m_undoListener.wasHiddenContextLeft() );
+ assertTrue( m_undoListener.hasContextBeenCancelled() );
+
+ // nesting hidden and normal contexts
+ m_undoListener.reset();
+ i_undoManager.reset();
+ final CustomUndoAction action0 = new CustomUndoAction( "action 0" );
+ i_undoManager.addUndoAction( action0 );
+ i_undoManager.enterUndoContext( "context 1" );
+ final CustomUndoAction action1 = new CustomUndoAction( "action 1" );
+ i_undoManager.addUndoAction( action1 );
+ i_undoManager.enterHiddenUndoContext();
+ final CustomUndoAction action2 = new CustomUndoAction( "action 2" );
+ i_undoManager.addUndoAction( action2 );
+ i_undoManager.enterUndoContext( "context 2" );
+ // is entering a hidden context rejected even at the nesting level > 0 (the above test was for nesting level == 0)?
+ caughtExpected = false;
+ try { i_undoManager.enterHiddenUndoContext(); }
+ catch( final EmptyUndoStackException e ) { caughtExpected = true; }
+ assertTrue( "at a nesting level > 0, denied hidden contexts does not work as expected", caughtExpected );
+ final CustomUndoAction action3 = new CustomUndoAction( "action 3" );
+ i_undoManager.addUndoAction( action3 );
+ i_undoManager.enterHiddenUndoContext();
+ assertEquals( "mixed hidden/normal context do are not properly notified", 4, m_undoListener.getCurrentUndoContextDepth() );
+ i_undoManager.leaveUndoContext();
+ assertTrue( "the left context was empty - why wasn't 'cancelled' notified?", m_undoListener.hasContextBeenCancelled() );
+ assertFalse( m_undoListener.wasContextLeft() );
+ assertFalse( m_undoListener.wasHiddenContextLeft() );
+ i_undoManager.leaveUndoContext();
+ i_undoManager.leaveUndoContext();
+ i_undoManager.leaveUndoContext();
+ i_undoManager.undo();
+ assertFalse( "one action too much has been undone", action0.undoCalled() );
+ assertTrue( action1.undoCalled() );
+ assertTrue( action2.undoCalled() );
+ assertTrue( action3.undoCalled() );
+ }
+
+
+ private XComponentContext getContext()
+ {
+ return m_connection.getComponentContext();
+ }
+
+
+ private XMultiServiceFactory getORB()
+ {
+ final XMultiServiceFactory xMSF1 = UnoRuntime.queryInterface(
+ XMultiServiceFactory.class, getContext().getServiceManager() );
+ return xMSF1;
+ }
+
+
+ @BeforeClass
+ public static void setUpConnection() throws java.lang.Exception
+ {
+ System.out.println( "--------------------------------------------------------------------------------" );
+ System.out.println( "starting class: " + UndoManager.class.getName() );
+ System.out.println( "connecting ..." );
+ m_connection.setUp();
+ }
+
+
+ @AfterClass
+ public static void tearDownConnection() throws InterruptedException, com.sun.star.uno.Exception
+ {
+ System.out.println();
+ System.out.println( "tearing down connection" );
+ m_connection.tearDown();
+ System.out.println( "finished class: " + UndoManager.class.getName() );
+ System.out.println( "--------------------------------------------------------------------------------" );
+ }
+
+
+ private static class CustomUndoAction implements XUndoAction, XComponent
+ {
+ CustomUndoAction()
+ {
+ m_title = "Custom Undo Action";
+ }
+
+ CustomUndoAction( final String i_title )
+ {
+ m_title = i_title;
+ }
+
+ public String getTitle()
+ {
+ return m_title;
+ }
+
+ public void undo() throws UndoFailedException
+ {
+ m_undoCalled = true;
+ }
+
+ public void redo() throws UndoFailedException
+ {
+ }
+
+ public void dispose()
+ {
+ m_disposed = true;
+ }
+
+ public void addEventListener( XEventListener xl )
+ {
+ fail( "addEventListener is not expected to be called in the course of this test" );
+ }
+
+ public void removeEventListener( XEventListener xl )
+ {
+ fail( "removeEventListener is not expected to be called in the course of this test" );
+ }
+
+ boolean undoCalled() { return m_undoCalled; }
+ boolean disposed() { return m_disposed; }
+
+ private final String m_title;
+ private boolean m_undoCalled = false;
+ private boolean m_disposed = false;
+ }
+
+ private static short FAIL_UNDO = 1;
+ private static short FAIL_REDO = 2;
+
+ private static class FailingUndoAction implements XUndoAction
+ {
+ FailingUndoAction( final short i_failWhich )
+ {
+ m_failWhich = i_failWhich;
+ }
+
+ public String getTitle()
+ {
+ return "failing undo";
+ }
+
+ public void undo() throws UndoFailedException
+ {
+ if ( m_failWhich != FAIL_REDO )
+ impl_throw();
+ }
+
+ public void redo() throws UndoFailedException
+ {
+ if ( m_failWhich != FAIL_UNDO )
+ impl_throw();
+ }
+
+ private void impl_throw() throws UndoFailedException
+ {
+ throw new UndoFailedException();
+ }
+
+ private final short m_failWhich;
+ }
+
+
+ private static class CountingUndoAction implements XUndoAction
+ {
+ CountingUndoAction( final int i_expectedOrder, final Object i_lock, final Integer[] i_actionsUndoneCounter )
+ {
+ m_expectedOrder = i_expectedOrder;
+ m_lock = i_lock;
+ m_actionsUndoneCounter = i_actionsUndoneCounter;
+ }
+
+ public String getTitle()
+ {
+ return "Counting Undo Action";
+ }
+
+ public void undo() throws UndoFailedException
+ {
+ synchronized( m_lock )
+ {
+ assertEquals( "Undo action called out of order", m_expectedOrder, m_actionsUndoneCounter[0].intValue() );
+ ++m_actionsUndoneCounter[0];
+ }
+ }
+
+ public void redo() throws UndoFailedException
+ {
+ fail( "CountingUndoAction.redo is not expected to be called in this test." );
+ }
+ private final int m_expectedOrder;
+ private final Object m_lock;
+ private Integer[] m_actionsUndoneCounter;
+ }
+
+
+ private static String getCallbackUndoContextTitle()
+ {
+ return "Some Unfinished Undo Context";
+ }
+
+
+ private static String getCallbackComponentServiceName()
+ {
+ return "org.openoffice.complex.sfx2.Callback";
+ }
+
+
+ /**
+ * a factory for a callback component which, at OOo runtime, is inserted into OOo's "component repository"
+ */
+ private class CallbackComponentFactory implements XSingleComponentFactory, XServiceInfo, XComponent
+ {
+ public Object createInstanceWithContext( XComponentContext i_context ) throws com.sun.star.uno.Exception
+ {
+ return new CallbackComponent();
+ }
+
+ public Object createInstanceWithArgumentsAndContext( Object[] i_arguments, XComponentContext i_context ) throws com.sun.star.uno.Exception
+ {
+ return createInstanceWithContext( i_context );
+ }
+
+ public String getImplementationName()
+ {
+ return "org.openoffice.complex.sfx2.CallbackComponent";
+ }
+
+ public boolean supportsService( String i_serviceName )
+ {
+ return i_serviceName.equals( getCallbackComponentServiceName() );
+ }
+
+ public String[] getSupportedServiceNames()
+ {
+ return new String[] { getCallbackComponentServiceName() };
+ }
+
+ @SuppressWarnings("unchecked")
+ public void dispose()
+ {
+ final EventObject event = new EventObject( this );
+
+ final ArrayList<XEventListener> eventListenersCopy = (ArrayList<XEventListener>)m_eventListeners.clone();
+ final Iterator<XEventListener> iter = eventListenersCopy.iterator();
+ while ( iter.hasNext() )
+ {
+ iter.next().disposing( event );
+ }
+ }
+
+ public void addEventListener( XEventListener i_listener )
+ {
+ if ( i_listener != null )
+ m_eventListeners.add( i_listener );
+ }
+
+ public void removeEventListener( XEventListener i_listener )
+ {
+ m_eventListeners.remove( i_listener );
+ }
+
+ private final ArrayList<XEventListener> m_eventListeners = new ArrayList<XEventListener>();
+ }
+
+
+ private class CallbackComponent implements XJob, XTypeProvider
+ {
+ CallbackComponent()
+ {
+ }
+
+ public Object execute( NamedValue[] i_parameters ) throws com.sun.star.lang.IllegalArgumentException, com.sun.star.uno.Exception
+ {
+ // this method is called from within the Basic script which is to check whether the OOo framework
+ // properly cleans up unfinished Undo contexts. It is called immediately after the context has been
+ // entered, so verify the expected Undo manager state.
+ assertEquals( getCallbackUndoContextTitle(), m_undoListener.getCurrentUndoContextTitle() );
+ assertEquals( 1, m_undoListener.getCurrentUndoContextDepth() );
+
+ synchronized( m_callbackCondition )
+ {
+ m_callbackCalled = true;
+ m_callbackCondition.notifyAll();
+ }
+ return m_closeAfterCallback ? "close" : "";
+ }
+
+ public Type[] getTypes()
+ {
+ final Class<?> interfaces[] = getClass().getInterfaces();
+ Type types[] = new Type[ interfaces.length ];
+ for ( int i = 0; i < interfaces.length; ++i )
+ types[i] = new Type(interfaces[i]);
+ return types;
+ }
+
+ public byte[] getImplementationId()
+ {
+ return new byte[0];
+ }
+ }
+
+ private static final OfficeConnection m_connection = new OfficeConnection();
+ private DocumentTest m_currentTestCase;
+ private OfficeDocument m_currentDocument;
+ private UndoListener m_undoListener;
+ private CallbackComponentFactory m_callbackFactory = null;
+ private boolean m_callbackCalled = false;
+ private boolean m_closeAfterCallback = false;
+ private final Object m_callbackCondition = new Object();
+}
diff --git a/sfx2/qa/complex/sfx2/testdocuments/CUSTOM.odt b/sfx2/qa/complex/sfx2/testdocuments/CUSTOM.odt
new file mode 100644
index 0000000000..831a8f451d
--- /dev/null
+++ b/sfx2/qa/complex/sfx2/testdocuments/CUSTOM.odt
Binary files differ
diff --git a/sfx2/qa/complex/sfx2/testdocuments/TEST.odt b/sfx2/qa/complex/sfx2/testdocuments/TEST.odt
new file mode 100644
index 0000000000..7c6f0b60f7
--- /dev/null
+++ b/sfx2/qa/complex/sfx2/testdocuments/TEST.odt
Binary files differ
diff --git a/sfx2/qa/complex/sfx2/testdocuments/TESTRDFA.odt b/sfx2/qa/complex/sfx2/testdocuments/TESTRDFA.odt
new file mode 100644
index 0000000000..d59739142d
--- /dev/null
+++ b/sfx2/qa/complex/sfx2/testdocuments/TESTRDFA.odt
Binary files differ
diff --git a/sfx2/qa/complex/sfx2/testdocuments/empty.rdf b/sfx2/qa/complex/sfx2/testdocuments/empty.rdf
new file mode 100644
index 0000000000..af62bab39d
--- /dev/null
+++ b/sfx2/qa/complex/sfx2/testdocuments/empty.rdf
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+
+<RDF
+ xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:s="http://www.w3.org/2000/01/rdf-schema#">
+
+<!--
+ This is the RDF Schema for the RDF data model as described in the
+ Resource Description Framework (RDF) Model and Syntax Specification
+ http://www.w3.org/TR/REC-rdf-syntax -->
+
+</RDF>
diff --git a/sfx2/qa/complex/sfx2/tools/TestDocument.java b/sfx2/qa/complex/sfx2/tools/TestDocument.java
new file mode 100644
index 0000000000..462c97fb99
--- /dev/null
+++ b/sfx2/qa/complex/sfx2/tools/TestDocument.java
@@ -0,0 +1,34 @@
+/*
+ * 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 .
+ */
+
+package complex.sfx2.tools;
+
+import java.io.File;
+import org.openoffice.test.OfficeFileUrl;
+import org.openoffice.test.Argument;
+
+public final class TestDocument {
+ public static String getUrl(String name) {
+ return OfficeFileUrl.getAbsolute(new File(Argument.get("tdoc"), name));
+ }
+ public static String getPath(String name) {
+ return new File(Argument.get("tdoc"), name).toString();
+ }
+
+ private TestDocument() {}
+}
diff --git a/sfx2/qa/complex/sfx2/tools/WriterHelper.java b/sfx2/qa/complex/sfx2/tools/WriterHelper.java
new file mode 100644
index 0000000000..3180d36071
--- /dev/null
+++ b/sfx2/qa/complex/sfx2/tools/WriterHelper.java
@@ -0,0 +1,210 @@
+/*
+ * 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 .
+ */
+package complex.sfx2.tools;
+
+import com.sun.star.accessibility.AccessibleRole;
+import com.sun.star.accessibility.XAccessible;
+import com.sun.star.accessibility.XAccessibleAction;
+import com.sun.star.accessibility.XAccessibleContext;
+import com.sun.star.awt.XExtendedToolkit;
+import com.sun.star.awt.XWindow;
+import com.sun.star.beans.PropertyValue;
+import com.sun.star.frame.XController;
+import com.sun.star.frame.XDesktop;
+import com.sun.star.frame.XDispatch;
+import com.sun.star.frame.XDispatchProvider;
+import com.sun.star.frame.XModel;
+import com.sun.star.lang.XComponent;
+import com.sun.star.lang.XMultiServiceFactory;
+import com.sun.star.text.XTextDocument;
+import com.sun.star.uno.UnoRuntime;
+import com.sun.star.util.URL;
+import com.sun.star.util.XCloseable;
+import com.sun.star.util.XURLTransformer;
+
+import util.AccessibilityTools;
+import util.WriterTools;
+
+/**
+ * Methods to open Writer docs
+ *
+ */
+public class WriterHelper {
+ private XMultiServiceFactory m_xMSF = null;
+
+ /**
+ * Creates a new instance of WriterHelper
+ *
+ * @param xMSF
+ * The MultiServiceFactory gained from the office
+ */
+ public WriterHelper(XMultiServiceFactory xMSF) {
+ this.m_xMSF = xMSF;
+ }
+
+ /**
+ * Opens an empty document
+ *
+ * @return a reference to the opened document is returned
+ */
+ public XTextDocument openEmptyDoc() {
+ return WriterTools.createTextDoc(m_xMSF);
+ }
+
+ /**
+ * Closes a given XTextDocument
+ *
+ * @param xTextDoc
+ * the text document to be closed
+ * @return if an error occurs the errormessage is returned and an empty
+ * String if not
+ */
+ public String closeDoc(XTextDocument xTextDoc) {
+ XCloseable closer = UnoRuntime.queryInterface(XCloseable.class,
+ xTextDoc);
+ String err = "";
+
+ try {
+ closer.close(true);
+ } catch (com.sun.star.util.CloseVetoException e) {
+ err = "couldn't close document " + e;
+ System.out.println(err);
+ }
+
+ return err;
+ }
+
+ private XTextDocument xLocalDoc = null;
+
+ /**
+ * a TextDocument is opened by pressing a button in a dialog given by
+ * uno-URL
+ *
+ * @param url
+ * the uno-URL of the dialog to be opened
+ * @param createButton
+ * the language dependent label of the button to be pressed
+ * @param destroyLocal
+ * if true the document that has been opened to dispatch the
+ * dialog is closed before the method returns, otherwise this
+ * document remains open
+ * @return returns the created Textdocument
+ */
+ public XTextDocument openFromDialog(String url, String createButton,
+ boolean destroyLocal) throws Exception {
+ xLocalDoc = WriterTools.createTextDoc(m_xMSF);
+ XComponent comp = UnoRuntime
+ .queryInterface(XComponent.class, xLocalDoc);
+
+ XModel aModel = UnoRuntime.queryInterface(XModel.class, comp);
+
+ XController xController = aModel.getCurrentController();
+
+ // Opening Dialog
+ XDispatchProvider xDispProv = UnoRuntime.queryInterface(
+ XDispatchProvider.class, xController.getFrame());
+ XURLTransformer xParser = UnoRuntime.queryInterface(
+ XURLTransformer.class,
+ m_xMSF.createInstance("com.sun.star.util.URLTransformer"));
+
+ // Because it's an in/out parameter
+ // we must use an array of URL objects.
+ URL[] aParseURL = new URL[] { new URL() };
+ aParseURL[0].Complete = url;
+ xParser.parseStrict(aParseURL);
+
+ XDispatch xDispatcher = xDispProv.queryDispatch(aParseURL[0], "",
+ com.sun.star.frame.FrameSearchFlag.SELF
+ | com.sun.star.frame.FrameSearchFlag.CHILDREN);
+ if (xDispatcher != null) {
+ PropertyValue[] dispatchArguments = new PropertyValue[0];
+ xDispatcher.dispatch(aParseURL[0], dispatchArguments);
+ }
+
+ if (createButton.length() > 1) {
+ XExtendedToolkit tk = getToolkit();
+ Object atw = tk.getActiveTopWindow();
+
+ XWindow xWindow = UnoRuntime.queryInterface(XWindow.class, atw);
+
+ XAccessible xRoot = AccessibilityTools.getAccessibleObject(xWindow);
+ XAccessibleContext buttonContext = AccessibilityTools
+ .getAccessibleObjectForRole(xRoot,
+ AccessibleRole.PUSH_BUTTON, createButton);
+
+ XAccessibleAction buttonAction = UnoRuntime.queryInterface(
+ XAccessibleAction.class, buttonContext);
+
+ try {
+ System.out
+ .println("Name: " + buttonContext.getAccessibleName());
+ buttonAction.doAccessibleAction(0);
+ } catch (com.sun.star.lang.IndexOutOfBoundsException e) {
+ System.out.println("Couldn't press button");
+ }
+
+ util.utils.waitForEventIdle(m_xMSF);
+ }
+
+ XDesktop xDesktop = getDesktop();
+
+ XTextDocument returnDoc = UnoRuntime.queryInterface(
+ XTextDocument.class, xDesktop.getCurrentComponent());
+
+ if (destroyLocal) {
+ closeDoc(xLocalDoc);
+ xLocalDoc = null;
+ }
+
+ return returnDoc;
+ }
+
+ public void closeFromDialog() {
+ closeDoc(xLocalDoc);
+ xLocalDoc = null;
+ }
+
+ /**
+ * creates an instance of com.sun.star.awt.Toolkit to query the
+ * XExtendedToolkit interface
+ *
+ * @return returns the gained XExtendedToolkit Interface
+ */
+ public XExtendedToolkit getToolkit() throws com.sun.star.uno.Exception {
+ Object toolkit = m_xMSF.createInstance("com.sun.star.awt.Toolkit");
+
+ XExtendedToolkit tk = UnoRuntime.queryInterface(XExtendedToolkit.class,
+ toolkit);
+
+ return tk;
+ }
+
+ /**
+ * creates an instance of com.sun.star.frame.Desktop to query the XDesktop
+ * interface
+ *
+ * @return returns the gained XDesktop interface
+ */
+ private XDesktop getDesktop() throws com.sun.star.uno.Exception {
+ Object desk = m_xMSF.createInstance("com.sun.star.frame.Desktop");
+
+ XDesktop xDesktop = UnoRuntime.queryInterface(XDesktop.class, desk);
+
+ return xDesktop;
+ }
+} \ No newline at end of file
diff --git a/sfx2/qa/complex/sfx2/undo/CalcDocumentTest.java b/sfx2/qa/complex/sfx2/undo/CalcDocumentTest.java
new file mode 100644
index 0000000000..a0dec8d06e
--- /dev/null
+++ b/sfx2/qa/complex/sfx2/undo/CalcDocumentTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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 .
+ */
+
+package complex.sfx2.undo;
+
+import org.openoffice.test.tools.SpreadsheetDocument;
+import com.sun.star.table.XCellRange;
+import com.sun.star.lang.XMultiServiceFactory;
+import com.sun.star.table.XCell;
+import com.sun.star.uno.UnoRuntime;
+import org.openoffice.test.tools.DocumentType;
+import static org.junit.Assert.*;
+
+/**
+ * implements the {@link DocumentTest} interface on top of a spreadsheet document
+ */
+public class CalcDocumentTest extends DocumentTestBase
+{
+ public CalcDocumentTest( final XMultiServiceFactory i_orb ) throws Exception
+ {
+ super( i_orb, DocumentType.CALC );
+ }
+
+ public String getDocumentDescription()
+ {
+ return "spreadsheet document";
+ }
+
+ public void initializeDocument() throws com.sun.star.uno.Exception
+ {
+ final XCell cellA1 = getCellA1();
+ cellA1.setValue( INIT_VALUE );
+ assertEquals( "initializing the cell value didn't work", cellA1.getValue(), INIT_VALUE, 0 );
+
+ XCellRange range = UnoRuntime.queryInterface( XCellRange.class,
+ ((SpreadsheetDocument)m_document).getSheet(0) );
+
+ for ( int i=0; i<12; ++i )
+ {
+ XCell cell = range.getCellByPosition( 1, i );
+ cell.setFormula( "" );
+ }
+ }
+
+ public void doSingleModification() throws com.sun.star.uno.Exception
+ {
+ final XCell cellA1 = getCellA1();
+ assertEquals( "initial cell value not as expected", INIT_VALUE, cellA1.getValue(), 0 );
+ cellA1.setValue( MODIFIED_VALUE );
+ assertEquals( "modified cell value not as expected", MODIFIED_VALUE, cellA1.getValue(), 0 );
+ }
+
+ public void verifyInitialDocumentState() throws com.sun.star.uno.Exception
+ {
+ final XCell cellA1 = getCellA1();
+ assertEquals( "cell A1 doesn't have its initial value", INIT_VALUE, cellA1.getValue(), 0 );
+
+ XCellRange range = UnoRuntime.queryInterface( XCellRange.class,
+ ((SpreadsheetDocument)m_document).getSheet(0) );
+ for ( int i=0; i<12; ++i )
+ {
+ final XCell cell = range.getCellByPosition( 1, i );
+ assertEquals( "Cell B" + (i+1) + " not having its initial value (an empty string)", "", cell.getFormula() );
+ }
+ }
+
+ public void verifySingleModificationDocumentState() throws com.sun.star.uno.Exception
+ {
+ final XCell cellA1 = getCellA1();
+ assertEquals( "cell A1 doesn't have the value which we gave it", MODIFIED_VALUE, cellA1.getValue(), 0 );
+ }
+
+ public int doMultipleModifications() throws com.sun.star.uno.Exception
+ {
+ XCellRange range = UnoRuntime.queryInterface( XCellRange.class,
+ ((SpreadsheetDocument)m_document).getSheet(0) );
+
+ final String[] months = new String[] {
+ "January", "February", "March", "April", "May", "June", "July", "August",
+ "September", "October", "November", "December" };
+ for ( int i=0; i<12; ++i )
+ {
+ final XCell cell = range.getCellByPosition( 1, i );
+ cell.setFormula( months[i] );
+ }
+ return 12;
+ }
+
+ private XCell getCellA1() throws com.sun.star.uno.Exception
+ {
+ XCellRange range = UnoRuntime.queryInterface( XCellRange.class,
+ ((SpreadsheetDocument)m_document).getSheet(0) );
+ return range.getCellByPosition( 0, 0 );
+ }
+
+ private static final double INIT_VALUE = 100.0;
+ private static final double MODIFIED_VALUE = 200.0;
+}
diff --git a/sfx2/qa/complex/sfx2/undo/ChartDocumentTest.java b/sfx2/qa/complex/sfx2/undo/ChartDocumentTest.java
new file mode 100644
index 0000000000..5d5ea95b26
--- /dev/null
+++ b/sfx2/qa/complex/sfx2/undo/ChartDocumentTest.java
@@ -0,0 +1,257 @@
+/*
+ * 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 .
+ */
+
+package complex.sfx2.undo;
+
+import com.sun.star.chart2.XAxis;
+import com.sun.star.chart2.XCoordinateSystem;
+import com.sun.star.chart2.XCoordinateSystemContainer;
+import com.sun.star.awt.Size;
+import com.sun.star.beans.XPropertySet;
+import com.sun.star.chart2.XChartDocument;
+import com.sun.star.chart2.XDiagram;
+import com.sun.star.container.XIndexAccess;
+import com.sun.star.document.UndoFailedException;
+import com.sun.star.document.XUndoAction;
+import com.sun.star.document.XUndoManager;
+import com.sun.star.document.XUndoManagerSupplier;
+import com.sun.star.drawing.XShape;
+import com.sun.star.embed.EmbedStates;
+import com.sun.star.embed.EmbedVerbs;
+import com.sun.star.embed.XEmbeddedObject;
+import com.sun.star.lang.IndexOutOfBoundsException;
+import com.sun.star.lang.WrappedTargetException;
+import com.sun.star.lang.XMultiServiceFactory;
+import com.sun.star.text.XTextContent;
+import com.sun.star.text.XTextRange;
+import com.sun.star.uno.UnoRuntime;
+import com.sun.star.view.XSelectionSupplier;
+import org.openoffice.test.tools.DocumentType;
+import org.openoffice.test.tools.OfficeDocument;
+import static org.junit.Assert.*;
+
+public class ChartDocumentTest implements DocumentTest
+{
+ public ChartDocumentTest( final XMultiServiceFactory i_orb ) throws com.sun.star.uno.Exception
+ {
+ m_textDocument = OfficeDocument.blankDocument( i_orb, DocumentType.WRITER );
+
+ // create an OLE shape in the document
+ final XMultiServiceFactory factory = UnoRuntime.queryInterface( XMultiServiceFactory.class, m_textDocument.getDocument() );
+ final String shapeServiceName = "com.sun.star.text.TextEmbeddedObject";
+ final XPropertySet shapeProps = UnoRuntime.queryInterface( XPropertySet.class, factory.createInstance( shapeServiceName ) );
+ shapeProps.setPropertyValue("CLSID", "12dcae26-281f-416f-a234-c3086127382e");
+
+ final XShape shape = UnoRuntime.queryInterface( XShape.class, shapeProps );
+ shape.setSize( new Size( 16000, 9000 ) );
+
+ final XTextContent chartTextContent = UnoRuntime.queryInterface( XTextContent.class, shapeProps );
+
+ final XSelectionSupplier selSupplier = UnoRuntime.queryInterface( XSelectionSupplier.class,
+ m_textDocument.getCurrentView().getController() );
+ final Object selection = selSupplier.getSelection();
+ final XTextRange textRange = getAssociatedTextRange( selection );
+ if ( textRange == null )
+ throw new RuntimeException( "can't locate a text range" );
+
+ // insert the chart
+ textRange.getText().insertTextContent(textRange, chartTextContent, false);
+
+ // retrieve the chart model
+ XChartDocument chartDoc = UnoRuntime.queryInterface( XChartDocument.class, shapeProps.getPropertyValue( "Model" ) );
+ // insert default chart for the test to use.
+ com.sun.star.chart2.XChartDocument xCD2 =
+ UnoRuntime.queryInterface(com.sun.star.chart2.XChartDocument.class, chartDoc);
+ xCD2.createDefaultChart();
+ m_chartDocument = new OfficeDocument( i_orb, chartDoc );
+
+ // actually activate the object
+ final XEmbeddedObject embeddedChart = UnoRuntime.queryInterface( XEmbeddedObject.class,
+ shapeProps.getPropertyValue( "EmbeddedObject" ) );
+ embeddedChart.doVerb( EmbedVerbs.MS_OLEVERB_SHOW );
+
+ final int state = embeddedChart.getCurrentState();
+ if ( state != EmbedStates.UI_ACTIVE )
+ fail( "unable to activate the embedded chart" );
+ }
+
+ public String getDocumentDescription()
+ {
+ return "chart document";
+ }
+
+ public void initializeDocument() throws com.sun.star.uno.Exception
+ {
+ final XPropertySet wallProperties = impl_getWallProperties();
+ wallProperties.setPropertyValue( "FillStyle", com.sun.star.drawing.FillStyle.SOLID );
+ wallProperties.setPropertyValue( "FillColor", 0x00FFFFFF );
+ }
+
+ public void closeDocument()
+ {
+ m_textDocument.close();
+ }
+
+ private XPropertySet impl_getWallProperties()
+ {
+ final XChartDocument chartDoc = UnoRuntime.queryInterface( XChartDocument.class, m_chartDocument.getDocument() );
+ final XDiagram diagram = chartDoc.getFirstDiagram();
+ final XPropertySet wallProperties = diagram.getWall();
+ return wallProperties;
+ }
+
+ private XPropertySet impl_getYAxisProperties() throws IndexOutOfBoundsException
+ {
+ XPropertySet axisProperties = null;
+ final XChartDocument chartDoc = UnoRuntime.queryInterface( XChartDocument.class, m_chartDocument.getDocument() );
+ final XDiagram diagram = chartDoc.getFirstDiagram();
+ final XCoordinateSystemContainer coordContainer = UnoRuntime.queryInterface( XCoordinateSystemContainer.class, diagram );
+ final XCoordinateSystem[] coordSystems = coordContainer.getCoordinateSystems();
+ final XCoordinateSystem coordSystem = coordSystems[0];
+ final XAxis primaryYAxis = coordSystem.getAxisByDimension( 1, 0 );
+ axisProperties = UnoRuntime.queryInterface( XPropertySet.class, primaryYAxis );
+ return axisProperties;
+ }
+
+ private XUndoManager impl_getUndoManager()
+ {
+ final XUndoManagerSupplier undoManagerSupp = UnoRuntime.queryInterface( XUndoManagerSupplier.class, m_chartDocument.getDocument() );
+ final XUndoManager undoManager = undoManagerSupp.getUndoManager();
+ return undoManager;
+ }
+
+ public void doSingleModification() throws com.sun.star.uno.Exception
+ {
+ final XPropertySet wallProperties = impl_getWallProperties();
+
+ // simulate an Undo action, as long as the chart implementation doesn't add Undo actions itself
+ final XUndoManager undoManager = impl_getUndoManager();
+ undoManager.addUndoAction( new PropertyUndoAction( wallProperties, "FillColor", 0xCCFF44 ) );
+ // (the UndoAction will actually set the property value)
+ }
+
+ public void verifyInitialDocumentState() throws com.sun.star.uno.Exception
+ {
+ final XPropertySet wallProperties = impl_getWallProperties();
+ assertEquals( 0x00FFFFFF, ((Integer)wallProperties.getPropertyValue( "FillColor" )).intValue() );
+ }
+
+ public void verifySingleModificationDocumentState() throws com.sun.star.uno.Exception
+ {
+ final XPropertySet wallProperties = impl_getWallProperties();
+ assertEquals( 0xCCFF44, ((Integer)wallProperties.getPropertyValue( "FillColor" )).intValue() );
+ }
+
+ public int doMultipleModifications() throws com.sun.star.uno.Exception
+ {
+ final XPropertySet axisProperties = impl_getYAxisProperties();
+
+ final XUndoManager undoManager = impl_getUndoManager();
+ undoManager.addUndoAction( new PropertyUndoAction( axisProperties, "LineWidth", 300 ) );
+ undoManager.addUndoAction( new PropertyUndoAction( axisProperties, "LineColor", 0x000000 ) );
+
+ return 2;
+ }
+
+ public OfficeDocument getDocument()
+ {
+ return m_chartDocument;
+ }
+
+ private XTextRange getAssociatedTextRange( final Object i_object ) throws WrappedTargetException, IndexOutOfBoundsException
+ {
+ // possible cases:
+ // 1. a container of other objects - e.g. selection of 0 to n text portions, or 1 to n drawing objects
+ final XIndexAccess indexer = UnoRuntime.queryInterface( XIndexAccess.class, i_object );
+ if ((indexer != null) && indexer.getCount() > 0) {
+ final int count = indexer.getCount();
+ for (int i = 0; i < count; ++i) {
+ final XTextRange range = getAssociatedTextRange( indexer.getByIndex(i) );
+ if (range != null) {
+ return range;
+ }
+ }
+ }
+ // 2. another TextContent, having an anchor we can use
+ final XTextContent textContent = UnoRuntime.queryInterface(XTextContent.class, i_object);
+ if (textContent != null) {
+ final XTextRange range = textContent.getAnchor();
+ if (range != null) {
+ return range;
+ }
+ }
+
+ // an object which supports XTextRange directly
+ final XTextRange range = UnoRuntime.queryInterface(XTextRange.class, i_object);
+ if (range != null) {
+ return range;
+ }
+
+ return null;
+ }
+
+ private static class PropertyUndoAction implements XUndoAction
+ {
+ PropertyUndoAction( final XPropertySet i_component, final String i_propertyName, final Object i_newValue ) throws com.sun.star.uno.Exception
+ {
+ m_component = i_component;
+ m_propertyName = i_propertyName;
+ m_newValue = i_newValue;
+
+ m_oldValue = i_component.getPropertyValue( m_propertyName );
+ i_component.setPropertyValue( m_propertyName, m_newValue );
+ }
+
+ public String getTitle()
+ {
+ return "some dummy Undo Action";
+ }
+
+ public void undo() throws UndoFailedException
+ {
+ try
+ {
+ m_component.setPropertyValue( m_propertyName, m_oldValue );
+ }
+ catch ( com.sun.star.uno.Exception ex )
+ {
+ throw new UndoFailedException( "", this, ex );
+ }
+ }
+
+ public void redo() throws UndoFailedException
+ {
+ try
+ {
+ m_component.setPropertyValue( m_propertyName, m_newValue );
+ }
+ catch ( com.sun.star.uno.Exception ex )
+ {
+ throw new UndoFailedException( "", this, ex );
+ }
+ }
+
+ private final XPropertySet m_component;
+ private final String m_propertyName;
+ private final Object m_oldValue;
+ private final Object m_newValue;
+ }
+
+ private final OfficeDocument m_textDocument;
+ private final OfficeDocument m_chartDocument;
+}
diff --git a/sfx2/qa/complex/sfx2/undo/DocumentTest.java b/sfx2/qa/complex/sfx2/undo/DocumentTest.java
new file mode 100644
index 0000000000..dd5fe4cf72
--- /dev/null
+++ b/sfx2/qa/complex/sfx2/undo/DocumentTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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 .
+ */
+
+package complex.sfx2.undo;
+
+import org.openoffice.test.tools.OfficeDocument;
+
+/**
+ * wrapper around an OfficeDocument, for running a standardized test procedure (related do Undo functionality)
+ * on the document.
+ *
+ */
+public interface DocumentTest
+{
+ /**
+ * returns a human-readable description for the document/type which the tests operates on
+ */
+ public String getDocumentDescription();
+
+ /**
+ * initializes the document to a state where the subsequent tests can be ran
+ */
+ public void initializeDocument() throws com.sun.star.uno.Exception;
+
+ /**
+ * closes the document which the test is based on
+ */
+ public void closeDocument();
+
+ /**
+ * does a simple modification to the document, which results in one Undo action being auto-generated
+ * by the OOo implementation
+ */
+ public void doSingleModification() throws com.sun.star.uno.Exception;
+
+ /**
+ * verifies the document is in the same state as after {@link #initializeDocument}
+ */
+ public void verifyInitialDocumentState() throws com.sun.star.uno.Exception;
+
+ /**
+ * verifies the document is in the state as expected after {@link #doSingleModification}
+ */
+ public void verifySingleModificationDocumentState() throws com.sun.star.uno.Exception;
+
+ /**
+ * does multiple modifications do the document, which would normally result in multiple Undo actions.
+ *
+ * The test framework will encapsulate the call into an {@link com.sun.star.document.XUndoManager#enterUndoContext} and
+ * {@link com.sun.star.document.XUndoManager#leaveUndoContext} call.
+ *
+ * @return
+ * the number of modifications done to the document. The caller assumes (and asserts) that the number
+ * of actions on the Undo stack equals this number.
+ */
+ public int doMultipleModifications() throws com.sun.star.uno.Exception;
+
+ /**
+ * returns the document which the test operates on
+ */
+ public OfficeDocument getDocument();
+}
diff --git a/sfx2/qa/complex/sfx2/undo/DocumentTestBase.java b/sfx2/qa/complex/sfx2/undo/DocumentTestBase.java
new file mode 100644
index 0000000000..d7a8c111b8
--- /dev/null
+++ b/sfx2/qa/complex/sfx2/undo/DocumentTestBase.java
@@ -0,0 +1,44 @@
+/*
+ * 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 .
+ */
+
+package complex.sfx2.undo;
+
+import com.sun.star.lang.XMultiServiceFactory;
+import com.sun.star.uno.Exception;
+import org.openoffice.test.tools.DocumentType;
+import org.openoffice.test.tools.OfficeDocument;
+
+abstract class DocumentTestBase implements DocumentTest
+{
+ DocumentTestBase( final XMultiServiceFactory i_orb, final DocumentType i_docType ) throws Exception
+ {
+ m_document = OfficeDocument.blankDocument( i_orb, i_docType );
+ }
+
+ public OfficeDocument getDocument()
+ {
+ return m_document;
+ }
+
+ public void closeDocument()
+ {
+ m_document.close();
+ }
+
+ protected final OfficeDocument m_document;
+}
diff --git a/sfx2/qa/complex/sfx2/undo/DrawDocumentTest.java b/sfx2/qa/complex/sfx2/undo/DrawDocumentTest.java
new file mode 100644
index 0000000000..ef4ff7fa09
--- /dev/null
+++ b/sfx2/qa/complex/sfx2/undo/DrawDocumentTest.java
@@ -0,0 +1,35 @@
+/*
+ * 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 .
+ */
+
+package complex.sfx2.undo;
+
+import com.sun.star.lang.XMultiServiceFactory;
+import org.openoffice.test.tools.DocumentType;
+
+public class DrawDocumentTest extends DrawingOrPresentationDocumentTest
+{
+ public DrawDocumentTest( XMultiServiceFactory i_orb ) throws com.sun.star.uno.Exception
+ {
+ super( i_orb, DocumentType.DRAWING );
+ }
+
+ public String getDocumentDescription()
+ {
+ return "drawing document";
+ }
+}
diff --git a/sfx2/qa/complex/sfx2/undo/DrawingOrPresentationDocumentTest.java b/sfx2/qa/complex/sfx2/undo/DrawingOrPresentationDocumentTest.java
new file mode 100644
index 0000000000..37b5d0d9cd
--- /dev/null
+++ b/sfx2/qa/complex/sfx2/undo/DrawingOrPresentationDocumentTest.java
@@ -0,0 +1,207 @@
+/*
+ * 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 .
+ */
+
+package complex.sfx2.undo;
+
+import com.sun.star.awt.Rectangle;
+import com.sun.star.document.XUndoManager;
+import com.sun.star.document.XUndoManagerSupplier;
+import com.sun.star.document.XUndoAction;
+import com.sun.star.awt.Point;
+import com.sun.star.awt.Size;
+import com.sun.star.beans.XPropertySet;
+import com.sun.star.drawing.CircleKind;
+import com.sun.star.drawing.XDrawPages;
+import com.sun.star.drawing.XDrawPagesSupplier;
+import com.sun.star.drawing.XShape;
+import com.sun.star.drawing.XShapes;
+import com.sun.star.lang.XMultiServiceFactory;
+import com.sun.star.uno.UnoRuntime;
+import org.openoffice.test.tools.DocumentType;
+import static org.junit.Assert.*;
+
+/**
+ * implements the {@link DocumentTest} interface on top of a drawing document
+ */
+public abstract class DrawingOrPresentationDocumentTest extends DocumentTestBase
+{
+ public DrawingOrPresentationDocumentTest( XMultiServiceFactory i_orb, final DocumentType i_docType ) throws com.sun.star.uno.Exception
+ {
+ super( i_orb, i_docType );
+ }
+
+ public void initializeDocument() throws com.sun.star.uno.Exception
+ {
+ // remove all shapes - Impress has two default shapes in a new doc; just get rid of them
+ final XShapes firstPageShapes = getFirstPageShapes();
+ while ( firstPageShapes.getCount() > 0 )
+ firstPageShapes.remove( UnoRuntime.queryInterface( XShape.class, firstPageShapes.getByIndex( 0 ) ) );
+ }
+
+ public void doSingleModification() throws com.sun.star.uno.Exception
+ {
+ // add a simple centered shape to the first page
+ Rectangle pagePlayground = impl_getFirstPagePlayground();
+ impl_createCircleShape(
+ ( pagePlayground.X + ( pagePlayground.Width - BIG_CIRCLE_SIZE ) / 2 ),
+ ( pagePlayground.Y + ( pagePlayground.Height - BIG_CIRCLE_SIZE ) / 2 ),
+ BIG_CIRCLE_SIZE,
+ FILL_COLOR
+ );
+ }
+
+ public void verifyInitialDocumentState() throws com.sun.star.uno.Exception
+ {
+ final XShapes firstPageShapes = getFirstPageShapes();
+ assertEquals( "there should be no shapes at all", 0, firstPageShapes.getCount() );
+ }
+
+ public void verifySingleModificationDocumentState() throws com.sun.star.uno.Exception
+ {
+ final XShapes firstPageShapes = getFirstPageShapes();
+ assertEquals( "there should be one shape, not more, not less", 1, firstPageShapes.getCount() );
+
+ final Object shape = firstPageShapes.getByIndex(0);
+ verifyShapeGeometry( shape, BIG_CIRCLE_SIZE, BIG_CIRCLE_SIZE );
+ final XPropertySet shapeProps = UnoRuntime.queryInterface( XPropertySet.class, shape );
+ assertEquals( "wrong circle type", CIRCLE_TYPE.getValue(), ((CircleKind)shapeProps.getPropertyValue( "CircleKind" )).getValue() );
+ //assertEquals( "wrong circle fill color", FILL_COLOR, ((Integer)shapeProps.getPropertyValue( "FillColor" )).intValue() );
+ // disable this particular check: A bug in the drawing layer API restores the FillColor to its
+ // default value upon re-insertion. This is issue #i115080#
+ }
+
+ public int doMultipleModifications() throws com.sun.star.uno.Exception
+ {
+ // add a simple centered shape to the first page
+ Rectangle pagePlayground = impl_getFirstPagePlayground();
+ impl_createCircleShape(
+ pagePlayground.X,
+ pagePlayground.Y,
+ SMALL_CIRCLE_SIZE,
+ ALTERNATE_FILL_COLOR
+ );
+ impl_createCircleShape(
+ pagePlayground.X + pagePlayground.Width - SMALL_CIRCLE_SIZE,
+ pagePlayground.Y,
+ SMALL_CIRCLE_SIZE,
+ ALTERNATE_FILL_COLOR
+ );
+ impl_createCircleShape(
+ pagePlayground.X,
+ pagePlayground.Y + pagePlayground.Height - SMALL_CIRCLE_SIZE,
+ SMALL_CIRCLE_SIZE,
+ ALTERNATE_FILL_COLOR
+ );
+ impl_createCircleShape(
+ pagePlayground.X + pagePlayground.Width - SMALL_CIRCLE_SIZE,
+ pagePlayground.Y + pagePlayground.Height - SMALL_CIRCLE_SIZE,
+ SMALL_CIRCLE_SIZE,
+ ALTERNATE_FILL_COLOR
+ );
+ return 4;
+ }
+
+ private void impl_createCircleShape( final int i_x, final int i_y, final int i_size, final int i_color ) throws com.sun.star.uno.Exception
+ {
+ final XPropertySet shapeProps = getDocument().createInstance( "com.sun.star.drawing.EllipseShape", XPropertySet.class );
+ shapeProps.setPropertyValue( "CircleKind", CIRCLE_TYPE );
+ shapeProps.setPropertyValue( "FillColor", i_color );
+
+ final XShape shape = UnoRuntime.queryInterface( XShape.class, shapeProps );
+ final Size shapeSize = new Size( i_size, i_size );
+ shape.setSize( shapeSize );
+ final Point shapePos = new Point( i_x, i_y );
+ shape.setPosition( shapePos );
+
+ final XShapes pageShapes = UnoRuntime.queryInterface( XShapes.class, getFirstPageShapes() );
+ pageShapes.add( shape );
+
+ // Sadly, Draw/Impress currently do not create Undo actions for programmatic changes to the document.
+ // Which renders the test here slightly useless ... unless we fake the Undo actions ourself.
+ final XUndoManagerSupplier suppUndoManager = UnoRuntime.queryInterface( XUndoManagerSupplier.class, getDocument().getDocument() );
+ final XUndoManager undoManager = suppUndoManager.getUndoManager();
+ undoManager.addUndoAction( new ShapeInsertionUndoAction( shape, pageShapes ) );
+ }
+
+ private Rectangle impl_getFirstPagePlayground() throws com.sun.star.uno.Exception
+ {
+ final XShapes firstPageShapes = getFirstPageShapes();
+ final XPropertySet firstPageProps = UnoRuntime.queryInterface( XPropertySet.class, firstPageShapes );
+ final int pageWidth = ((Integer)firstPageProps.getPropertyValue( "Width" )).intValue();
+ final int pageHeight = ((Integer)firstPageProps.getPropertyValue( "Height" )).intValue();
+ final int borderLeft = ((Integer)firstPageProps.getPropertyValue( "BorderLeft" )).intValue();
+ final int borderTop = ((Integer)firstPageProps.getPropertyValue( "BorderTop" )).intValue();
+ final int borderRight = ((Integer)firstPageProps.getPropertyValue( "BorderRight" )).intValue();
+ final int borderBottom = ((Integer)firstPageProps.getPropertyValue( "BorderBottom" )).intValue();
+ return new Rectangle( borderLeft, borderTop, pageWidth - borderLeft - borderRight, pageHeight - borderTop - borderBottom );
+ }
+
+ /**
+ * returns the XShapes interface of the first page of our drawing document
+ */
+ private XShapes getFirstPageShapes() throws com.sun.star.uno.Exception
+ {
+ final XDrawPagesSupplier suppPages = UnoRuntime.queryInterface( XDrawPagesSupplier.class, getDocument().getDocument() );
+ final XDrawPages pages = suppPages.getDrawPages();
+ return UnoRuntime.queryInterface( XShapes.class, pages.getByIndex( 0 ) );
+ }
+
+ /**
+ * verifies the given shape has the given size
+ */
+ private void verifyShapeGeometry( final Object i_shapeObject, final int i_expectedWidth, final int i_expectedHeight )
+ {
+ final XShape shape = UnoRuntime.queryInterface( XShape.class, i_shapeObject );
+ final Size shapeSize = shape.getSize();
+ assertEquals( "unexpected shape width", i_expectedWidth, shapeSize.Width );
+ assertEquals( "unexpected shape height", i_expectedHeight, shapeSize.Height );
+ }
+
+ private static class ShapeInsertionUndoAction implements XUndoAction
+ {
+ ShapeInsertionUndoAction( final XShape i_shape, final XShapes i_shapeCollection )
+ {
+ m_shape = i_shape;
+ m_shapeCollection = i_shapeCollection;
+ }
+
+ public String getTitle()
+ {
+ return "insert shape";
+ }
+
+ public void undo()
+ {
+ m_shapeCollection.remove( m_shape );
+ }
+
+ public void redo()
+ {
+ m_shapeCollection.add( m_shape );
+ }
+
+ private final XShape m_shape;
+ private final XShapes m_shapeCollection;
+ }
+
+ private static CircleKind CIRCLE_TYPE = CircleKind.FULL;
+ private static int FILL_COLOR = 0xCC2244;
+ private static int ALTERNATE_FILL_COLOR = 0x44CC22;
+ private static int BIG_CIRCLE_SIZE = 5000;
+ private static int SMALL_CIRCLE_SIZE = 2000;
+}
diff --git a/sfx2/qa/complex/sfx2/undo/ImpressDocumentTest.java b/sfx2/qa/complex/sfx2/undo/ImpressDocumentTest.java
new file mode 100644
index 0000000000..8f9cb5771d
--- /dev/null
+++ b/sfx2/qa/complex/sfx2/undo/ImpressDocumentTest.java
@@ -0,0 +1,35 @@
+/*
+ * 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 .
+ */
+
+package complex.sfx2.undo;
+
+import com.sun.star.lang.XMultiServiceFactory;
+import org.openoffice.test.tools.DocumentType;
+
+public class ImpressDocumentTest extends DrawingOrPresentationDocumentTest
+{
+ public ImpressDocumentTest( XMultiServiceFactory i_orb ) throws com.sun.star.uno.Exception
+ {
+ super( i_orb, DocumentType.PRESENTATION );
+ }
+
+ public String getDocumentDescription()
+ {
+ return "presentation document";
+ }
+}
diff --git a/sfx2/qa/complex/sfx2/undo/WriterDocumentTest.java b/sfx2/qa/complex/sfx2/undo/WriterDocumentTest.java
new file mode 100644
index 0000000000..624c2d7d24
--- /dev/null
+++ b/sfx2/qa/complex/sfx2/undo/WriterDocumentTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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 .
+ */
+
+package complex.sfx2.undo;
+
+import com.sun.star.text.XTextRange;
+import com.sun.star.beans.XPropertySet;
+import com.sun.star.table.XCell;
+import com.sun.star.table.XCellRange;
+import com.sun.star.text.XTextCursor;
+import com.sun.star.text.XTextTable;
+import com.sun.star.text.XText;
+import com.sun.star.text.XTextDocument;
+import com.sun.star.lang.XMultiServiceFactory;
+import com.sun.star.uno.UnoRuntime;
+import org.openoffice.test.tools.DocumentType;
+import static org.junit.Assert.*;
+
+/**
+ * implements the {@link DocumentTest} interface on top of a spreadsheet document
+ */
+public class WriterDocumentTest extends DocumentTestBase
+{
+ public WriterDocumentTest( final XMultiServiceFactory i_orb ) throws com.sun.star.uno.Exception
+ {
+ super( i_orb, DocumentType.WRITER );
+ }
+
+ public String getDocumentDescription()
+ {
+ return "text document";
+ }
+
+ public void initializeDocument() throws com.sun.star.uno.Exception
+ {
+ // TODO?
+ }
+
+ public void doSingleModification() throws com.sun.star.uno.Exception
+ {
+ final XTextDocument textDoc = UnoRuntime.queryInterface( XTextDocument.class, getDocument().getDocument() );
+ final XText docText = textDoc.getText();
+ docText.setString( s_blindText );
+ }
+
+ public void verifyInitialDocumentState() throws com.sun.star.uno.Exception
+ {
+ final XTextDocument textDoc = UnoRuntime.queryInterface( XTextDocument.class, getDocument().getDocument() );
+ final XText docText = textDoc.getText();
+ assertEquals( "document should be empty", "", docText.getString() );
+ }
+
+ public void verifySingleModificationDocumentState() throws com.sun.star.uno.Exception
+ {
+ final XTextDocument textDoc = UnoRuntime.queryInterface( XTextDocument.class, getDocument().getDocument() );
+ final XText docText = textDoc.getText();
+ assertEquals( "blind text not found", s_blindText, docText.getString() );
+ }
+
+ public int doMultipleModifications() throws com.sun.star.uno.Exception
+ {
+ final XTextDocument textDoc = UnoRuntime.queryInterface( XTextDocument.class, getDocument().getDocument() );
+ final XText docText = textDoc.getText();
+
+ int expectedUndoActions = 0;
+
+ // create a cursor
+ final XTextCursor cursor = docText.createTextCursor();
+
+ // create a table
+ final XTextTable textTable = UnoRuntime.queryInterface( XTextTable.class,
+ getDocument().createInstance( "com.sun.star.text.TextTable" ) );
+ textTable.initialize( 3, 3 );
+ final XPropertySet tableProps = UnoRuntime.queryInterface( XPropertySet.class, textTable );
+ tableProps.setPropertyValue( "BackColor", 0xCCFF44 );
+
+ // insert the table into the doc
+ docText.insertTextContent( cursor, textTable, false );
+ ++expectedUndoActions; //FIXME this will create 2 actions! currently the event is sent for every individual action; should it be sent for top-level actions only? how many internal actions are created is an implementation detail!
+ ++expectedUndoActions;
+
+ // write some content into the center cell
+ final XCellRange cellRange = UnoRuntime.queryInterface( XCellRange.class, textTable );
+ final XCell centerCell = cellRange.getCellByPosition( 1, 1 );
+ final XTextRange cellText = UnoRuntime.queryInterface( XTextRange.class, centerCell );
+ cellText.setString( "Undo Manager API Test" );
+ ++expectedUndoActions;
+
+ // give it another color
+ final XPropertySet cellProps = UnoRuntime.queryInterface( XPropertySet.class, centerCell );
+ cellProps.setPropertyValue( "BackColor", 0x44CCFF );
+ ++expectedUndoActions;
+
+ return expectedUndoActions;
+ }
+
+ private static final String s_blindText =
+ "Lorem ipsum dolor. Sit amet penatibus. A cum turpis. Aenean ac eu. " +
+ "Ligula est urna nulla vestibulum ullamcorper. Nec sit in amet tincidunt mus. " +
+ "Tellus sagittis mi. Suscipit cursus in vestibulum in eros ipsum felis cursus lectus " +
+ "nunc quis condimentum in risus nec wisi aenean luctus hendrerit magna habitasse commodo orci. " +
+ "Nisl etiam quis. Vestibulum justo eleifend aliquet luctus sed turpis volutpat ullamcorper " +
+ "aliquam penatibus sagittis pede tincidunt egestas. Nibh massa lectus. Sem mattis purus morbi " +
+ "scelerisque turpis donec urna phasellus. Quis at lacus. Viverra mauris mollis. " +
+ "Dolor tincidunt condimentum.";
+}
diff --git a/sfx2/qa/cppunit/data/reload-page.odg b/sfx2/qa/cppunit/data/reload-page.odg
new file mode 100644
index 0000000000..0e9cf08649
--- /dev/null
+++ b/sfx2/qa/cppunit/data/reload-page.odg
Binary files differ
diff --git a/sfx2/qa/cppunit/doc.cxx b/sfx2/qa/cppunit/doc.cxx
new file mode 100644
index 0000000000..9eea6d2939
--- /dev/null
+++ b/sfx2/qa/cppunit/doc.cxx
@@ -0,0 +1,151 @@
+/* -*- 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 <test/unoapi_test.hxx>
+
+#include <com/sun/star/view/XSelectionSupplier.hpp>
+#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
+#include <com/sun/star/beans/PropertyAttribute.hpp>
+#include <com/sun/star/beans/XPropertyAccess.hpp>
+
+#include <comphelper/propertyvalue.hxx>
+#include <sfx2/objsh.hxx>
+#include <sfx2/sfxbasemodel.hxx>
+#include <osl/file.hxx>
+#include <comphelper/sequenceashashmap.hxx>
+#include <comphelper/propertysequence.hxx>
+#include <comphelper/sequence.hxx>
+
+using namespace com::sun::star;
+
+namespace
+{
+/// Covers sfx2/source/doc/ fixes.
+class Test : public UnoApiTest
+{
+public:
+ Test()
+ : UnoApiTest("/sfx2/qa/cppunit/data/")
+ {
+ }
+};
+
+CPPUNIT_TEST_FIXTURE(Test, testNoGrabBagShape)
+{
+ // Load a document and select the first shape.
+ css::uno::Sequence<css::beans::PropertyValue> aArgs{ comphelper::makePropertyValue("ReadOnly",
+ true) };
+ mxComponent = loadFromDesktop("private:factory/simpress", "", aArgs);
+ uno::Reference<frame::XModel> xModel(mxComponent, uno::UNO_QUERY);
+ uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(xModel, uno::UNO_QUERY);
+ uno::Reference<container::XIndexAccess> xDrawPage(
+ xDrawPagesSupplier->getDrawPages()->getByIndex(0), uno::UNO_QUERY);
+ uno::Any aShape = xDrawPage->getByIndex(0);
+ uno::Reference<view::XSelectionSupplier> xController(xModel->getCurrentController(),
+ uno::UNO_QUERY);
+ xController->select(aShape);
+
+ // See if it has a signing certificate associated.
+ auto pBaseModel = dynamic_cast<SfxBaseModel*>(xModel.get());
+ CPPUNIT_ASSERT(pBaseModel);
+ SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell();
+
+ // Without the accompanying fix in place, this test would have failed with:
+ // An uncaught exception of type com.sun.star.beans.UnknownPropertyException
+ // which was not caught later, resulting in a crash.
+ pObjectShell->GetSignPDFCertificate();
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTempFilePath)
+{
+ // Create a test file in a directory that contains the URL-encoded "testÿ" string.
+ mxComponent = loadFromDesktop("private:factory/swriter");
+ auto pBaseModel = dynamic_cast<SfxBaseModel*>(mxComponent.get());
+ CPPUNIT_ASSERT(pBaseModel);
+ OUString aTargetDir
+ = m_directories.getURLFromWorkdir(u"CppunitTest/sfx2_doc.test.user/test%25C3%25Bf");
+ osl::Directory::create(aTargetDir);
+ OUString aTargetFile = aTargetDir + "/test.odt";
+ css::uno::Sequence<css::beans::PropertyValue> aArgs{ comphelper::makePropertyValue(
+ "FilterName", OUString("writer8")) };
+ pBaseModel->storeAsURL(aTargetFile, aArgs);
+ mxComponent->dispose();
+
+ // Load it and export to PDF.
+ mxComponent = loadFromDesktop(aTargetFile);
+ pBaseModel = dynamic_cast<SfxBaseModel*>(mxComponent.get());
+ OUString aPdfTarget = aTargetDir + "/test.pdf";
+ css::uno::Sequence<css::beans::PropertyValue> aPdfArgs{ comphelper::makePropertyValue(
+ "FilterName", OUString("writer_pdf_Export")) };
+ // Without the accompanying fix in place, this test would have failed on Windows with:
+ // An uncaught exception of type com.sun.star.io.IOException
+ // because we first tried to create a temp file next to test.odt in a directory named
+ // "test%25C3%25Bf" instead of a directory named "test%C3%Bf".
+ pBaseModel->storeToURL(aPdfTarget, aPdfArgs);
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testSetDocumentPropertiesUpdate)
+{
+ // Given a document with 3 custom props, 2 Zotero ones and one other:
+ mxComponent = loadFromDesktop("private:factory/swriter");
+ auto pBaseModel = dynamic_cast<SfxBaseModel*>(mxComponent.get());
+ CPPUNIT_ASSERT(pBaseModel);
+ uno::Reference<document::XDocumentProperties> xDP = pBaseModel->getDocumentProperties();
+ uno::Reference<beans::XPropertyContainer> xUDP = xDP->getUserDefinedProperties();
+ xUDP->addProperty("ZOTERO_PREF_1", beans::PropertyAttribute::REMOVABLE,
+ uno::Any(OUString("foo")));
+ xUDP->addProperty("ZOTERO_PREF_2", beans::PropertyAttribute::REMOVABLE,
+ uno::Any(OUString("bar")));
+ xUDP->addProperty("OTHER", beans::PropertyAttribute::REMOVABLE, uno::Any(OUString("baz")));
+
+ // When updating the Zotero ones (1 update, 1 removal):
+ std::vector<beans::PropertyValue> aArgsVec = comphelper::JsonToPropertyValues(R"json(
+{
+ "UpdatedProperties": {
+ "type": "[]com.sun.star.beans.PropertyValue",
+ "value": {
+ "NamePrefix": {
+ "type": "string",
+ "value": "ZOTERO_PREF_"
+ },
+ "UserDefinedProperties": {
+ "type": "[]com.sun.star.beans.PropertyValue",
+ "value": {
+ "ZOTERO_PREF_1": {
+ "type": "string",
+ "value": "test"
+ }
+ }
+ }
+ }
+ }
+}
+)json"_ostr);
+ uno::Sequence<beans::PropertyValue> aArgs = comphelper::containerToSequence(aArgsVec);
+ dispatchCommand(mxComponent, ".uno:SetDocumentProperties", aArgs);
+
+ // Then make sure that OTHER is still there and that ZOTERO_PREF_1 + ZOTERO_PREF_2 gets updated
+ // to the new value of a single ZOTERO_PREF_1:
+ uno::Reference<beans::XPropertyAccess> xUDPAccess(xUDP, uno::UNO_QUERY);
+ comphelper::SequenceAsHashMap aMap(xUDPAccess->getPropertyValues());
+ auto it = aMap.find("ZOTERO_PREF_1");
+ CPPUNIT_ASSERT(it != aMap.end());
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: test
+ // - Actual : foo
+ // i.e. ZOTERO_PREF_1 was not updated.
+ CPPUNIT_ASSERT_EQUAL(OUString("test"), it->second.get<OUString>());
+ CPPUNIT_ASSERT(bool(aMap.find("ZOTERO_PREF_2") == aMap.end()));
+ CPPUNIT_ASSERT(aMap.find("OTHER") != aMap.end());
+}
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/qa/cppunit/misc/hello.odt b/sfx2/qa/cppunit/misc/hello.odt
new file mode 100644
index 0000000000..23ce6a4db9
--- /dev/null
+++ b/sfx2/qa/cppunit/misc/hello.odt
Binary files differ
diff --git a/sfx2/qa/cppunit/test_classification.cxx b/sfx2/qa/cppunit/test_classification.cxx
new file mode 100644
index 0000000000..ffa90089fd
--- /dev/null
+++ b/sfx2/qa/cppunit/test_classification.cxx
@@ -0,0 +1,112 @@
+/* -*- 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 <test/unoapi_test.hxx>
+
+#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/frame/DispatchHelper.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+
+#include <comphelper/processfactory.hxx>
+#include <comphelper/propertysequence.hxx>
+
+using namespace ::com::sun::star;
+
+namespace
+{
+
+/// Tests the handling of the .uno:ClassificationApply command in various applications.
+class ClassificationTest : public UnoApiTest
+{
+ void testClassification();
+
+public:
+ ClassificationTest()
+ : UnoApiTest("/sfx2/qa/cppunit/data/")
+ {
+ }
+
+ void testWriter();
+ void testCalc();
+ void testImpress();
+
+ CPPUNIT_TEST_SUITE(ClassificationTest);
+ CPPUNIT_TEST(testWriter);
+ CPPUNIT_TEST(testCalc);
+ CPPUNIT_TEST(testImpress);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void ClassificationTest::testClassification()
+{
+ uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
+ {
+ {"Name", uno::Any(OUString("Non-Business"))},
+ {"Type", uno::Any(OUString("urn:bails:ExportControl:"))},
+ }));
+ dispatchCommand(mxComponent, ".uno:ClassificationApply", aPropertyValues);
+
+ uno::Reference<document::XDocumentPropertiesSupplier> xDocumentPropertiesSupplier(mxComponent, uno::UNO_QUERY);
+ CPPUNIT_ASSERT(xDocumentPropertiesSupplier.is());
+ uno::Reference<document::XDocumentProperties> xDocumentProperties = xDocumentPropertiesSupplier->getDocumentProperties();
+ uno::Reference<beans::XPropertySet> xPropertySet(xDocumentProperties->getUserDefinedProperties(), uno::UNO_QUERY);
+ uno::Any aAny = xPropertySet->getPropertyValue("urn:bails:ExportControl:BusinessAuthorizationCategory:Identifier");
+ CPPUNIT_ASSERT_EQUAL(OUString("urn:example:tscp:1:non-business"), aAny.get<OUString>());
+
+ aPropertyValues = comphelper::InitPropertySequence(
+ {
+ {"Name", uno::Any(OUString("Confidential"))},
+ {"Type", uno::Any(OUString("urn:bails:NationalSecurity:"))},
+ });
+ dispatchCommand(mxComponent, ".uno:ClassificationApply", aPropertyValues);
+ aAny = xPropertySet->getPropertyValue("urn:bails:NationalSecurity:BusinessAuthorizationCategory:Identifier");
+ CPPUNIT_ASSERT_EQUAL(OUString("urn:example:tscp:1:confidential"), aAny.get<OUString>());
+
+ aPropertyValues = comphelper::InitPropertySequence(
+ {
+ {"Name", uno::Any(OUString("Internal Only"))},
+ {"Type", uno::Any(OUString("urn:bails:IntellectualProperty:"))},
+ });
+ dispatchCommand(mxComponent, ".uno:ClassificationApply", aPropertyValues);
+ aAny = xPropertySet->getPropertyValue("urn:bails:IntellectualProperty:BusinessAuthorizationCategory:Identifier");
+ CPPUNIT_ASSERT_EQUAL(OUString("urn:example:tscp:1:internal-only"), aAny.get<OUString>());
+}
+
+void ClassificationTest::testWriter()
+{
+ // Test SID_CLASSIFICATION_APPLY handling in SwDocShell::Execute().
+ mxComponent = loadFromDesktop("private:factory/swriter", "com.sun.star.text.TextDocument");
+ // This resulted in a beans::UnknownPropertyException when the request wasn't handled.
+ testClassification();
+}
+
+void ClassificationTest::testCalc()
+{
+ // Test SID_CLASSIFICATION_APPLY handling in ScFormatShell::ExecuteStyle().
+ mxComponent = loadFromDesktop("private:factory/scalc", "com.sun.star.sheet.SpreadsheetDocument");
+ // This resulted in a beans::UnknownPropertyException when the request wasn't handled.
+ testClassification();
+}
+
+void ClassificationTest::testImpress()
+{
+ // Test SID_CLASSIFICATION_APPLY handling in sd::DrawViewShell::FuTemporary().
+ mxComponent = loadFromDesktop("private:factory/simpress", "com.sun.star.presentation.PresentationDocument");
+ // This resulted in a beans::UnknownPropertyException when the request wasn't handled.
+ testClassification();
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(ClassificationTest);
+
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/qa/cppunit/test_controlleritem.cxx b/sfx2/qa/cppunit/test_controlleritem.cxx
new file mode 100644
index 0000000000..75d2205367
--- /dev/null
+++ b/sfx2/qa/cppunit/test_controlleritem.cxx
@@ -0,0 +1,58 @@
+/* -*- 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/types.h>
+#include <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/plugin/TestPlugIn.h>
+
+#include <sfx2/ctrlitem.hxx>
+
+namespace {
+
+class ControllerItemTest
+ : public ::CppUnit::TestFixture
+{
+public:
+ void test();
+
+ CPPUNIT_TEST_SUITE(ControllerItemTest);
+ CPPUNIT_TEST(test);
+ CPPUNIT_TEST_SUITE_END();
+
+private:
+};
+
+bool bDeleted = false;
+
+class FooController : public SfxControllerItem {
+public:
+ FooController() : SfxControllerItem() {}
+ virtual ~FooController() override { bDeleted = true; }
+};
+
+void ControllerItemTest::test()
+{
+ FooController *pController(new FooController());
+
+ // TESTME: binding, un-binding, re-binding, IsBound, SetId etc.
+
+ pController->dispose();
+ delete pController;
+ CPPUNIT_ASSERT( bDeleted );
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(ControllerItemTest);
+
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/qa/cppunit/test_metadatable.cxx b/sfx2/qa/cppunit/test_metadatable.cxx
new file mode 100644
index 0000000000..459f635d00
--- /dev/null
+++ b/sfx2/qa/cppunit/test_metadatable.cxx
@@ -0,0 +1,248 @@
+/* -*- 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 <com/sun/star/lang/IllegalArgumentException.hpp>
+
+#include <sfx2/Metadatable.hxx>
+#include <sfx2/XmlIdRegistry.hxx>
+
+#include <memory>
+
+
+using namespace ::com::sun::star;
+
+
+namespace {
+
+class MetadatableTest
+ : public ::CppUnit::TestFixture
+{
+public:
+ void test();
+
+ CPPUNIT_TEST_SUITE(MetadatableTest);
+ CPPUNIT_TEST(test);
+ CPPUNIT_TEST_SUITE_END();
+
+private:
+};
+
+class MockMetadatable
+ : public ::sfx2::Metadatable
+{
+private:
+ ::sfx2::IXmlIdRegistry & m_rRegistry;
+
+public:
+ MockMetadatable(::sfx2::IXmlIdRegistry & i_rReg,
+ bool const i_isInClip = false)
+ : m_rRegistry(i_rReg)
+ , m_bInClipboard(i_isInClip), m_bInUndo(false), m_bInContent(true) {}
+ bool m_bInClipboard;
+ bool m_bInUndo;
+ bool m_bInContent;
+ virtual bool IsInClipboard() const override { return m_bInClipboard; }
+ virtual bool IsInUndo() const override { return m_bInUndo; }
+ virtual bool IsInContent() const override { return m_bInContent; }
+ virtual ::sfx2::IXmlIdRegistry& GetRegistry() override { return m_rRegistry; }
+ virtual css::uno::Reference< css::rdf::XMetadatable > MakeUnoObject() override { return nullptr; }
+};
+
+void MetadatableTest::test()
+{
+ std::unique_ptr< ::sfx2::IXmlIdRegistry > const pReg(
+ ::sfx2::createXmlIdRegistry(false) );
+ std::unique_ptr< ::sfx2::IXmlIdRegistry > const pRegClip(
+ ::sfx2::createXmlIdRegistry(true) );
+
+ MockMetadatable m1(*pReg);
+ MockMetadatable m2(*pReg);
+ MockMetadatable m3(*pReg);
+ MockMetadatable m4(*pReg);
+ MockMetadatable m5(*pReg);
+ OUString empty;
+ OUString content( "content.xml" );
+ OUString sid3( "id3" );
+ OUString sid4( "id4" );
+ beans::StringPair id1(content, "id1");
+ beans::StringPair id2(content, "id2");
+ beans::StringPair id3(content, sid3);
+ beans::StringPair id4("styles.xml", sid4);
+ beans::StringPair id3e(empty, sid3);
+ beans::StringPair id4e(empty, sid4);
+ m1.SetMetadataReference(id1);
+ CPPUNIT_ASSERT_MESSAGE("set failed", bool(m1.GetMetadataReference() == id1));
+ try {
+ m2.SetMetadataReference(id1);
+ CPPUNIT_ASSERT_MESSAGE("set duplicate succeeded", false);
+ } catch (const lang::IllegalArgumentException &) { }
+ m1.SetMetadataReference(id1);
+ CPPUNIT_ASSERT_MESSAGE("set failed (existing)",
+ bool(m1.GetMetadataReference() == id1));
+ m1.EnsureMetadataReference();
+ CPPUNIT_ASSERT_MESSAGE("ensure failed (existing)",
+ bool(m1.GetMetadataReference() == id1));
+
+ m2.EnsureMetadataReference();
+ beans::StringPair m2id(m2.GetMetadataReference());
+ CPPUNIT_ASSERT_MESSAGE("ensure failed", !m2id.Second.isEmpty());
+ m2.EnsureMetadataReference();
+ CPPUNIT_ASSERT_MESSAGE("ensure failed (idempotent)",
+ bool(m2.GetMetadataReference() == m2id));
+
+ m1.m_bInUndo = true;
+ CPPUNIT_ASSERT_MESSAGE("move to undo failed",
+ m1.GetMetadataReference().Second.isEmpty());
+
+ m1.m_bInUndo = false;
+ CPPUNIT_ASSERT_MESSAGE("move from undo failed",
+ bool(m1.GetMetadataReference() == id1));
+
+ m1.m_bInUndo = true;
+ try {
+ m2.SetMetadataReference(id1); // steal!
+ } catch (lang::IllegalArgumentException &) {
+ CPPUNIT_FAIL("set duplicate to undo failed");
+ }
+ m1.m_bInUndo = false;
+ CPPUNIT_ASSERT_MESSAGE("move from undo: duplicate",
+ m1.GetMetadataReference().Second.isEmpty());
+
+ m3.RegisterAsCopyOf(m2);
+ CPPUNIT_ASSERT_MESSAGE("copy: source", bool(m2.GetMetadataReference() == id1));
+ CPPUNIT_ASSERT_MESSAGE("copy: duplicate",
+ m3.GetMetadataReference().Second.isEmpty());
+ m4.RegisterAsCopyOf(m3);
+ CPPUNIT_ASSERT_MESSAGE("copy: source", bool(m2.GetMetadataReference() == id1));
+ CPPUNIT_ASSERT_MESSAGE("copy: duplicate",
+ m3.GetMetadataReference().Second.isEmpty());
+ CPPUNIT_ASSERT_MESSAGE("copy: duplicate",
+ m4.GetMetadataReference().Second.isEmpty());
+ m2.m_bInUndo = true;
+ CPPUNIT_ASSERT_MESSAGE("duplicate to undo",
+ bool(m3.GetMetadataReference() == id1));
+ CPPUNIT_ASSERT_MESSAGE("duplicate to undo",
+ m2.GetMetadataReference().Second.isEmpty());
+ m2.m_bInUndo = false;
+ CPPUNIT_ASSERT_MESSAGE("duplicate from undo",
+ bool(m2.GetMetadataReference() == id1));
+ CPPUNIT_ASSERT_MESSAGE("duplicate from undo",
+ m3.GetMetadataReference().Second.isEmpty());
+
+ m4.EnsureMetadataReference(); // new!
+ beans::StringPair m4id(m4.GetMetadataReference());
+ CPPUNIT_ASSERT_MESSAGE("ensure on duplicate",
+ !m4id.Second.isEmpty());
+ CPPUNIT_ASSERT_MESSAGE("ensure on duplicate",
+ !(m4id == id1));
+
+ MockMetadatable mc1(*pRegClip, true); // in clipboard
+ MockMetadatable mc2(*pRegClip, true);
+ MockMetadatable mc3(*pRegClip, true);
+ MockMetadatable mc4(*pRegClip, true);
+ MockMetadatable m2p(*pReg);
+ MockMetadatable m3p(*pReg);
+
+ mc1.SetMetadataReference(id2);
+ CPPUNIT_ASSERT_MESSAGE("set failed", bool(mc1.GetMetadataReference() == id2));
+ try {
+ mc2.SetMetadataReference(id2);
+ CPPUNIT_FAIL("set duplicate succeeded");
+ } catch (const lang::IllegalArgumentException &) { }
+ mc1.SetMetadataReference(id2);
+ CPPUNIT_ASSERT_MESSAGE("set failed (existing)",
+ bool(mc1.GetMetadataReference() == id2));
+ mc1.EnsureMetadataReference();
+ CPPUNIT_ASSERT_MESSAGE("ensure failed (existing)",
+ bool(mc1.GetMetadataReference() == id2));
+ mc2.EnsureMetadataReference();
+ beans::StringPair mc2id(mc2.GetMetadataReference());
+ CPPUNIT_ASSERT_MESSAGE("ensure failed", !mc2id.Second.isEmpty());
+ mc2.EnsureMetadataReference();
+ CPPUNIT_ASSERT_MESSAGE("ensure failed (idempotent)",
+ bool(mc2.GetMetadataReference() == mc2id));
+ mc2.RemoveMetadataReference();
+ CPPUNIT_ASSERT_MESSAGE("remove failed",
+ mc2.GetMetadataReference().Second.isEmpty());
+
+ // set up mc2 as copy of m2 and mc3 as copy of m3
+ mc3.RegisterAsCopyOf(m3);
+ CPPUNIT_ASSERT_MESSAGE("copy to clipboard (latent)",
+ mc3.GetMetadataReference().Second.isEmpty() );
+ mc2.RegisterAsCopyOf(m2);
+ CPPUNIT_ASSERT_MESSAGE("copy to clipboard (non-latent)",
+ bool(mc2.GetMetadataReference() == id1));
+ // paste mc2 to m2p and mc3 to m3p
+ m2p.RegisterAsCopyOf(mc2);
+ CPPUNIT_ASSERT_MESSAGE("paste from clipboard (non-latent)",
+ m2p.GetMetadataReference().Second.isEmpty() );
+ m3p.RegisterAsCopyOf(mc3);
+ CPPUNIT_ASSERT_MESSAGE("paste from clipboard (latent)",
+ m3p.GetMetadataReference().Second.isEmpty() );
+ // delete m2, m2p, m3
+ m2.RemoveMetadataReference();
+ CPPUNIT_ASSERT_MESSAGE("remove failed",
+ m2.GetMetadataReference().Second.isEmpty());
+ CPPUNIT_ASSERT_MESSAGE("paste-remove (non-latent)",
+ bool(m2p.GetMetadataReference() == id1));
+ m2p.RemoveMetadataReference();
+ CPPUNIT_ASSERT_MESSAGE("remove failed",
+ m2p.GetMetadataReference().Second.isEmpty());
+ CPPUNIT_ASSERT_MESSAGE("paste-remove2 (non-latent)",
+ bool(m3.GetMetadataReference() == id1));
+ m3.RemoveMetadataReference();
+ CPPUNIT_ASSERT_MESSAGE("remove failed",
+ m3.GetMetadataReference().Second.isEmpty());
+ CPPUNIT_ASSERT_MESSAGE("paste-remove (latent)",
+ bool(m3p.GetMetadataReference() == id1));
+ // delete mc2
+ mc2.SetMetadataReference(beans::StringPair());
+ CPPUNIT_ASSERT_MESSAGE("in clipboard becomes non-latent",
+ mc3.GetMetadataReference().Second.isEmpty() );
+ // paste mc2
+ m2p.RegisterAsCopyOf(mc2);
+ CPPUNIT_ASSERT_MESSAGE("remove-paste",
+ m2p.GetMetadataReference().Second.isEmpty());
+ CPPUNIT_ASSERT_MESSAGE("remove-paste (stolen)",
+ bool(m3p.GetMetadataReference() == id1));
+
+ // auto-detect stream
+ m5.SetMetadataReference(id3e);
+ CPPUNIT_ASSERT_MESSAGE("auto-detect (content)",
+ bool(m5.GetMetadataReference() == id3));
+ m5.m_bInContent = false;
+ m5.SetMetadataReference(id4e);
+ CPPUNIT_ASSERT_MESSAGE("auto-detect (styles)",
+ bool(m5.GetMetadataReference() == id4));
+}
+
+
+CPPUNIT_TEST_SUITE_REGISTRATION(MetadatableTest);
+
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/qa/cppunit/test_misc.cxx b/sfx2/qa/cppunit/test_misc.cxx
new file mode 100644
index 0000000000..d80a037f6f
--- /dev/null
+++ b/sfx2/qa/cppunit/test_misc.cxx
@@ -0,0 +1,201 @@
+/* -*- 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/types.h>
+
+#ifndef _WIN32
+#include <sys/stat.h>
+#include <unistd.h>
+#endif
+#include <memory>
+
+#include <cppunit/TestAssert.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/plugin/TestPlugIn.h>
+
+#include <com/sun/star/beans/PropertyState.hpp>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/document/DocumentProperties.hpp>
+#include <com/sun/star/packages/zip/ZipFileAccess.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/frame/XStorable.hpp>
+
+#include <test/unoapixml_test.hxx>
+
+#include <unotools/ucbstreamhelper.hxx>
+#include <comphelper/propertysequence.hxx>
+#include <comphelper/processfactory.hxx>
+#include <sfx2/app.hxx>
+#include <osl/file.hxx>
+
+
+using namespace ::com::sun::star;
+
+
+namespace {
+
+class MiscTest
+ : public UnoApiXmlTest
+{
+public:
+ MiscTest()
+ : UnoApiXmlTest("/sfx2/qa/cppunit/misc/")
+ {
+ }
+
+ virtual void registerNamespaces(xmlXPathContextPtr& pXmlXpathCtx) override
+ {
+ // ODF
+ xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("office"), BAD_CAST("urn:oasis:names:tc:opendocument:xmlns:office:1.0"));
+ xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("meta"), BAD_CAST("urn:oasis:names:tc:opendocument:xmlns:meta:1.0"));
+ xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("dc"), BAD_CAST("http://purl.org/dc/elements/1.1/"));
+ // used in testCustomMetadata
+ xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("foo"), BAD_CAST("http://foo.net"));
+ xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("baz"), BAD_CAST("http://baz.net"));
+ }
+};
+
+CPPUNIT_TEST_FIXTURE(MiscTest, testODFCustomMetadata)
+{
+ uno::Reference<document::XDocumentProperties> const xProps(
+ ::com::sun::star::document::DocumentProperties::create(m_xContext));
+
+ OUString const url(m_directories.getURLFromSrc(u"/sfx2/qa/complex/sfx2/testdocuments/CUSTOM.odt"));
+ xProps->loadFromMedium(url, uno::Sequence<beans::PropertyValue>());
+ CPPUNIT_ASSERT_EQUAL(OUString(""), xProps->getAuthor());
+ uno::Sequence<beans::PropertyValue> mimeArgs({
+ beans::PropertyValue("MediaType", -1, uno::Any(OUString("application/vnd.oasis.opendocument.text")), beans::PropertyState_DIRECT_VALUE)
+ });
+ xProps->storeToMedium(maTempFile.GetURL(), mimeArgs);
+
+ // check that custom metadata is preserved
+ xmlDocUniquePtr pXmlDoc = parseExport("meta.xml");
+ assertXPathContent(pXmlDoc, "/office:document-meta/office:meta/bork"_ostr, "bork");
+ assertXPath(pXmlDoc, "/office:document-meta/office:meta/foo:bar"_ostr, 1);
+ assertXPath(pXmlDoc, "/office:document-meta/office:meta/foo:bar/baz:foo"_ostr, 1);
+ assertXPath(pXmlDoc, "/office:document-meta/office:meta/foo:bar/baz:foo[@baz:bar='foo']"_ostr);
+ assertXPathContent(pXmlDoc, "/office:document-meta/office:meta/foo:bar/foo:baz"_ostr, "bar");
+}
+
+CPPUNIT_TEST_FIXTURE(MiscTest, testNoThumbnail)
+{
+ // Load a document.
+ loadFromFile(u"hello.odt");
+
+ // Save it with the NoThumbnail option and assert that it has no thumbnail.
+#ifndef _WIN32
+ mode_t nMask = umask(022);
+#endif
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ CPPUNIT_ASSERT(xStorable.is());
+ uno::Sequence<beans::PropertyValue> aProperties(
+ comphelper::InitPropertySequence({ { "NoThumbnail", uno::Any(true) } }));
+ osl::File::remove(maTempFile.GetURL());
+ xStorable->storeToURL(maTempFile.GetURL(), aProperties);
+ uno::Reference<packages::zip::XZipFileAccess2> xZipFile
+ = packages::zip::ZipFileAccess::createWithURL(m_xContext, maTempFile.GetURL());
+ CPPUNIT_ASSERT(!xZipFile->hasByName("Thumbnails/thumbnail.png"));
+
+#ifndef _WIN32
+ // Check permissions of the URL after store.
+ osl::DirectoryItem aItem;
+ CPPUNIT_ASSERT_EQUAL(osl::DirectoryItem::E_None,
+ osl::DirectoryItem::get(maTempFile.GetURL(), aItem));
+
+ osl::FileStatus aStatus(osl_FileStatus_Mask_Attributes);
+ CPPUNIT_ASSERT_EQUAL(osl::DirectoryItem::E_None, aItem.getFileStatus(aStatus));
+
+ // The following checks used to fail in the past, osl_File_Attribute_GrpRead was not set even if
+ // umask requested so:
+ CPPUNIT_ASSERT(aStatus.getAttributes() & osl_File_Attribute_GrpRead);
+ CPPUNIT_ASSERT(aStatus.getAttributes() & osl_File_Attribute_OthRead);
+
+ // Now "save as" again to trigger the "overwrite" case.
+ xStorable->storeToURL(maTempFile.GetURL(), {});
+ CPPUNIT_ASSERT_EQUAL(osl::DirectoryItem::E_None, aItem.getFileStatus(aStatus));
+ // The following check used to fail in the past, result had temp file
+ // permissions.
+ CPPUNIT_ASSERT(aStatus.getAttributes() & osl_File_Attribute_GrpRead);
+
+ umask(nMask);
+#endif
+}
+
+CPPUNIT_TEST_FIXTURE(MiscTest, testHardLinks)
+{
+#ifndef _WIN32
+ OUString aTargetDir = m_directories.getURLFromWorkdir(u"/CppunitTest/sfx2_misc.test.user/");
+ const OUString aURL(aTargetDir + "hello.odt");
+ osl::File::copy(createFileURL(u"hello.odt"), aURL);
+ OUString aTargetPath;
+ osl::FileBase::getSystemPathFromFileURL(aURL, aTargetPath);
+ OString aOld = aTargetPath.toUtf8();
+ aTargetPath += ".2";
+ OString aNew = aTargetPath.toUtf8();
+ int nRet = link(aOld.getStr(), aNew.getStr());
+ CPPUNIT_ASSERT_EQUAL(0, nRet);
+
+ mxComponent = loadFromDesktop(aURL, "com.sun.star.text.TextDocument");
+
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ xStorable->store();
+
+ struct stat buf;
+ // coverity[fs_check_call] - this is legitimate in the context of this test
+ nRet = stat(aOld.getStr(), &buf);
+ CPPUNIT_ASSERT_EQUAL(0, nRet);
+ // This failed: hard link count was 1, the hard link broke on store.
+ CPPUNIT_ASSERT(buf.st_nlink > 1);
+
+ // Test that symlinks are preserved as well.
+ nRet = remove(aNew.getStr());
+ CPPUNIT_ASSERT_EQUAL(0, nRet);
+ nRet = symlink(aOld.getStr(), aNew.getStr());
+ CPPUNIT_ASSERT_EQUAL(0, nRet);
+ xStorable->storeToURL(aURL + ".2", {});
+ nRet = lstat(aNew.getStr(), &buf);
+ CPPUNIT_ASSERT_EQUAL(0, nRet);
+ // This failed, the hello.odt.2 symlink was replaced with a real file.
+ CPPUNIT_ASSERT(bool(S_ISLNK(buf.st_mode)));
+#endif
+}
+
+CPPUNIT_TEST_FIXTURE(MiscTest, testOverwrite)
+{
+ // tdf#60237 - try to overwrite an existing file using the different settings of the Overwrite option
+ mxComponent
+ = loadFromDesktop(maTempFile.GetURL(), "com.sun.star.text.TextDocument");
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ CPPUNIT_ASSERT(xStorable.is());
+
+ // overwrite the file using the default case of the Overwrite option (true)
+ CPPUNIT_ASSERT_NO_THROW(xStorable->storeToURL(maTempFile.GetURL(), {}));
+
+ // explicitly overwrite the file using the Overwrite option
+ CPPUNIT_ASSERT_NO_THROW(xStorable->storeToURL(
+ maTempFile.GetURL(),
+ comphelper::InitPropertySequence({ { "Overwrite", uno::Any(true) } })));
+
+ try
+ {
+ // overwrite an existing file with the Overwrite flag set to false
+ xStorable->storeToURL(maTempFile.GetURL(), comphelper::InitPropertySequence(
+ { { "Overwrite", uno::Any(false) } }));
+ CPPUNIT_ASSERT_MESSAGE("We expect an exception on overwriting an existing file", false);
+ }
+ catch (const css::uno::Exception&)
+ {
+ }
+}
+
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/qa/cppunit/view.cxx b/sfx2/qa/cppunit/view.cxx
new file mode 100644
index 0000000000..725cb5d02c
--- /dev/null
+++ b/sfx2/qa/cppunit/view.cxx
@@ -0,0 +1,64 @@
+/* -*- 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 <test/unoapi_test.hxx>
+
+#include <com/sun/star/drawing/XDrawView.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+
+#include <sfx2/app.hxx>
+#include <sfx2/sfxsids.hrc>
+#include <sfx2/viewfrm.hxx>
+#include <svl/itemset.hxx>
+#include <svl/intitem.hxx>
+#include <sfx2/request.hxx>
+#include <sfx2/bindings.hxx>
+
+using namespace com::sun::star;
+
+/// Covers sfx2/source/view/ fixes.
+class Sfx2ViewTest : public UnoApiTest
+{
+public:
+ Sfx2ViewTest()
+ : UnoApiTest("/sfx2/qa/cppunit/data/")
+ {
+ }
+};
+
+CPPUNIT_TEST_FIXTURE(Sfx2ViewTest, testReloadPage)
+{
+ // Load a document, which has 2 pages.
+ loadFromFile(u"reload-page.odg");
+
+ // Reload, and request to start on page 2.
+ SfxViewFrame* pFrame = SfxViewFrame::Current();
+ SfxAllItemSet aSet(SfxGetpApp()->GetPool());
+ aSet.Put(SfxInt32Item(SID_PAGE_NUMBER, 1));
+ SfxRequest aReq(SID_RELOAD, SfxCallMode::SLOT, aSet);
+ pFrame->ExecReload_Impl(aReq);
+ uno::Reference<frame::XModel> xModel = SfxObjectShell::Current()->GetBaseModel();
+ mxComponent = xModel;
+
+ // Check the current page after reload.
+ uno::Reference<drawing::XDrawView> xController(xModel->getCurrentController(), uno::UNO_QUERY);
+ uno::Reference<beans::XPropertySet> xPage(xController->getCurrentPage(), uno::UNO_QUERY);
+ sal_Int32 nPage{};
+ xPage->getPropertyValue("Number") >>= nPage;
+
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 2
+ // - Actual : 1
+ // i.e. the document was opened on page 1, not page 2, SID_PAGE_NUMBER was ignored.
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(2), nPage);
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/qa/python/check_sidebar.py b/sfx2/qa/python/check_sidebar.py
new file mode 100644
index 0000000000..59cc955b80
--- /dev/null
+++ b/sfx2/qa/python/check_sidebar.py
@@ -0,0 +1,169 @@
+# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-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/.
+#
+
+import unittest
+import unohelper
+import os
+from org.libreoffice.unotest import UnoInProcess
+
+from com.sun.star.ui import XSidebarProvider
+from com.sun.star.ui import XDecks
+from com.sun.star.ui import XDeck
+from com.sun.star.ui import XPanels
+from com.sun.star.ui import XPanel
+
+class CheckSidebar(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ cls._uno = UnoInProcess()
+ cls._uno.setUp()
+
+ @classmethod
+ def tearDownClass(cls):
+ cls._uno.tearDown()
+
+ def test_check_sidebar(self):
+
+ xDoc = self.__class__._uno.openEmptyDoc( url = "private:factory/scalc", bHidden = False, bReadOnly = False)
+ xController = xDoc.getCurrentController()
+
+ xSidebar = xController.getSidebar()
+ assert(xSidebar)
+
+ xSidebar.setVisible(True)
+ isVisible = xSidebar.isVisible()
+ self.assertTrue ( xSidebar.isVisible() )
+
+ # TODO: does not work in unit test context
+# xSidebar.setVisible(False)
+# isVisible = xSidebar.isVisible()
+# assert( not isVisible )
+# xSidebar.setVisible(True)
+
+ xSidebar.showDecks(False)
+ xSidebar.showDecks(True)
+
+ xDecks = xSidebar.getDecks()
+
+ first_deck_name = "PropertyDeck";
+
+ deck_element_names = xDecks.getElementNames()
+ assert ( first_deck_name in deck_element_names )
+ assert ( xDecks.hasByName(first_deck_name) )
+
+ decks_count = len(xDecks)
+ self.assertEqual ( 5, decks_count )
+
+ xDeck = xDecks[first_deck_name]
+ assert ( xDeck )
+ assert ( xDeck.getId() == first_deck_name )
+
+ new_deck_title = "New title"
+ xDeck.setTitle(new_deck_title)
+ assert ( xDeck.getTitle() == new_deck_title )
+
+ xDeck.moveFirst()
+ initial_index = xDeck.getOrderIndex()
+ self.assertEqual(100, initial_index)
+
+ xDeck.moveLast()
+ assert ( xDeck.getOrderIndex() > initial_index )
+
+ initial_index = xDeck.getOrderIndex()
+ xDeck.moveFirst()
+ assert ( xDeck.getOrderIndex() < initial_index )
+
+ initial_index = xDeck.getOrderIndex()
+ xDeck.moveDown()
+ assert ( xDeck.getOrderIndex() > initial_index )
+
+ initial_index = xDeck.getOrderIndex()
+ xDeck.moveUp()
+ assert ( xDeck.getOrderIndex() < initial_index )
+
+ xPanels = xDeck.getPanels()
+
+ panels_count = len(xPanels)
+ self.assertEqual ( panels_count, 5 )
+
+ first_panel_name = self.getFirstPanel(xPanels)
+
+ panel_element_names = xPanels.getElementNames()
+ assert ( first_panel_name in panel_element_names )
+ assert ( xPanels.hasByName(first_panel_name) )
+
+ xPanel = xPanels[first_panel_name]
+ assert ( xPanel )
+ assert ( xPanel.getId() == first_panel_name )
+
+ new_title = "New title"
+ xPanel.setTitle(new_title)
+ assert ( xPanel.getTitle() == new_title )
+
+ initial_index = xPanel.getOrderIndex()
+ xPanel.moveLast()
+ assert ( xPanel.getOrderIndex() > initial_index )
+
+ initial_index = xPanel.getOrderIndex()
+ xPanel.moveFirst()
+ assert ( xPanel.getOrderIndex() < initial_index )
+
+ initial_index = xPanel.getOrderIndex()
+ xPanel.moveDown()
+ assert ( xPanel.getOrderIndex() > initial_index )
+
+ initial_index = xPanel.getOrderIndex()
+ xPanel.moveUp()
+ assert ( xPanel.getOrderIndex() < initial_index )
+
+ xPanel.collapse()
+ assert( not xPanel.isExpanded() )
+
+ last_panel_name = self.getLastPanel(xPanels)
+
+ other_panel = xPanels[last_panel_name]
+ other_panel.expand(False)
+ assert( other_panel.isExpanded() )
+
+ xPanel.expand(True)
+ assert( xPanel.isExpanded() )
+ assert( not other_panel.isExpanded() )
+
+ # close the document
+ xDoc.dispose()
+
+ def getFirstPanel(self, xPanels):
+
+ panel_name = ""
+ cur_index = 10000
+
+ for panel in xPanels:
+ if panel.getOrderIndex() < cur_index:
+ panel_name = panel.getId()
+ cur_index = panel.getOrderIndex()
+
+ return panel_name
+
+ def getLastPanel(self, xPanels):
+
+ panel_name = ""
+ cur_index = 0
+
+ for panel in xPanels:
+ if panel.getOrderIndex() > cur_index:
+ panel_name = panel.getId()
+ cur_index = panel.getOrderIndex()
+
+ return panel_name
+
+if __name__ == "__main__":
+ unittest.main()
+
+# vim: set shiftwidth=4 softtabstop=4 expandtab:
diff --git a/sfx2/qa/python/check_sidebar_registry.py b/sfx2/qa/python/check_sidebar_registry.py
new file mode 100644
index 0000000000..47b8eecdef
--- /dev/null
+++ b/sfx2/qa/python/check_sidebar_registry.py
@@ -0,0 +1,89 @@
+# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-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/.
+#
+
+import unittest
+import unohelper
+import os
+from org.libreoffice.unotest import UnoInProcess
+import uno
+
+class CheckSidebarRegistry(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ cls._uno = UnoInProcess()
+ cls._uno.setUp()
+
+ @classmethod
+ def tearDownClass(cls):
+ cls._uno.tearDown()
+
+ def test_sidebar_registry(self):
+
+ # assert(result) after whole processing to list defective nodes at once
+ result = True
+
+ #open registry node in Sidebar.xcu
+ config_provider = self.createUnoService("com.sun.star.configuration.ConfigurationProvider")
+
+ param = uno.createUnoStruct('com.sun.star.beans.PropertyValue')
+ param.Name = "nodepath"
+
+
+ # Deck names consistency
+
+ param.Value = "org.openoffice.Office.UI.Sidebar/Content/DeckList"
+
+ sidebar_decks_settings = config_provider.createInstanceWithArguments("com.sun.star.configuration.ConfigurationAccess",
+ (param, ))
+ for nodeName in sidebar_decks_settings:
+
+ node = sidebar_decks_settings[nodeName]
+
+ if (node.Id != nodeName):
+ print("\nNon-consistent sidebar.xcu Deck registry names", nodeName, node.Id)
+ result = False
+
+ # panel names consistency
+
+ param.Value = "org.openoffice.Office.UI.Sidebar/Content/PanelList"
+
+ sidebar_panels_settings = config_provider.createInstanceWithArguments("com.sun.star.configuration.ConfigurationAccess",
+ (param, ))
+ for nodeName in sidebar_panels_settings:
+
+ node = sidebar_panels_settings[nodeName]
+
+ if (node.Id != nodeName):
+ print("\nNon-consistent sidebar.xcu Panel registry names", nodeName, node.Id)
+ result = False
+
+ # is panel bound to an existing Deck ?
+ FoundDeckId = False
+ for deckNodeName in sidebar_decks_settings:
+ deck_node = sidebar_decks_settings[deckNodeName]
+ if (node.DeckId == deck_node.Id):
+ FoundDeckId = True
+ if not FoundDeckId:
+ print("\nNon existing DeckId for the panel ",node.Id)
+ result = False
+
+ # trigger the overall result. details of each error have already be printed
+ assert(result)
+
+
+ def createUnoService(self, serviceName):
+
+ sm = uno.getComponentContext().ServiceManager
+ return sm.createInstanceWithContext(serviceName, uno.getComponentContext())
+
+if __name__ == "__main__":
+ unittest.main()
+
+# vim: set shiftwidth=4 softtabstop=4 expandtab:
diff --git a/sfx2/qa/uitest/doc/data/pdf-sign.pdf b/sfx2/qa/uitest/doc/data/pdf-sign.pdf
new file mode 100644
index 0000000000..8dedb6998b
--- /dev/null
+++ b/sfx2/qa/uitest/doc/data/pdf-sign.pdf
Binary files differ
diff --git a/sfx2/qa/uitest/doc/objserv.py b/sfx2/qa/uitest/doc/objserv.py
new file mode 100644
index 0000000000..627469ae93
--- /dev/null
+++ b/sfx2/qa/uitest/doc/objserv.py
@@ -0,0 +1,24 @@
+#
+# 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/.
+#
+
+from uitest.framework import UITestCase
+from uitest.uihelper.common import get_url_for_data_file
+
+
+# Test for sfx2/source/doc/objserv.cxx.
+class Test(UITestCase):
+
+ def testPdfSigning(self):
+ # Start Impress.
+ with self.ui_test.load_file(get_url_for_data_file("pdf-sign.pdf")) as impress_doc:
+
+ # Now use File -> Digital signatures -> Digital signatures.
+ with self.ui_test.execute_dialog_through_command(".uno:Signature", close_button="close"):
+ # Without the accompanying fix in place, this test would have failed with:
+ # uno.com.sun.star.uno.RuntimeException: Could not find child with id: close vcl/source/uitest/uiobject.cxx:452
+ pass
+
+# vim: set shiftwidth=4 softtabstop=4 expandtab:
diff --git a/sfx2/qa/unit/data/sfx2-dialogs-test.txt b/sfx2/qa/unit/data/sfx2-dialogs-test.txt
new file mode 100644
index 0000000000..18dcbacf6a
--- /dev/null
+++ b/sfx2/qa/unit/data/sfx2-dialogs-test.txt
@@ -0,0 +1,72 @@
+# -*- 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 sfx2 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
+
+sfx/ui/documentpropertiesdialog.ui
+sfx/ui/descriptioninfopage.ui
+sfx/ui/documentinfopage.ui
+sfx/ui/custominfopage.ui
+sfx/ui/cmisinfopage.ui
+sfx/ui/documentfontspage.ui
+sfx/ui/managestylepage.ui
+sfx/ui/optprintpage.ui
+sfx/ui/securityinfopage.ui
+sfx/ui/helpcontentpage.ui
+sfx/ui/helpindexpage.ui
+sfx/ui/helpsearchpage.ui
+sfx/ui/helpbookmarkpage.ui
+sfx/ui/licensedialog.ui
+sfx/ui/linkeditdialog.ui
+sfx/ui/bookmarkdialog.ui
+sfx/ui/checkin.ui
+sfx/ui/editdurationdialog.ui
+sfx/ui/inputdialog.ui
+sfx/ui/newstyle.ui
+sfx/ui/password.ui
+sfx/ui/versionsofdialog.ui
+sfx/ui/versioncommentdialog.ui
+sfx/ui/versionscmis.ui
+sfx/ui/loadtemplatedialog.ui
+sfx/ui/templatedlg.ui
+sfx/ui/printeroptionsdialog.ui
+sfx/ui/searchdialog.ui
+sfx/ui/alienwarndialog.ui
+sfx/ui/errorfindemaildialog.ui
+sfx/ui/querysavedialog.ui
+sfx/ui/floatingrecord.ui
+sfx/ui/helpcontrol.ui
+sfx/ui/startcenter.ui
+sfx/ui/cmisline.ui
+sfx/ui/autoredactdialog.ui
diff --git a/sfx2/qa/unit/sfx2-dialogs-test.cxx b/sfx2/qa/unit/sfx2-dialogs-test.cxx
new file mode 100644
index 0000000000..6e23237bc3
--- /dev/null
+++ b/sfx2/qa/unit/sfx2-dialogs-test.cxx
@@ -0,0 +1,58 @@
+/* -*- 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 sfx2
+class Sfx2DialogsTest : 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:
+ Sfx2DialogsTest();
+
+ // try to open a dialog
+ void openAnyDialog();
+
+ CPPUNIT_TEST_SUITE(Sfx2DialogsTest);
+ CPPUNIT_TEST(openAnyDialog);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+Sfx2DialogsTest::Sfx2DialogsTest() {}
+
+void Sfx2DialogsTest::registerKnownDialogsByID(mapType& /*rKnownDialogs*/)
+{
+ // fill map of known dialogs
+}
+
+VclPtr<VclAbstractDialog> Sfx2DialogsTest::createDialogByID(sal_uInt32 /*nID*/) { return nullptr; }
+
+void Sfx2DialogsTest::openAnyDialog()
+{
+ /// process input file containing the UXMLDescriptions of the dialogs to dump
+ processDialogBatchFile(u"sfx2/qa/unit/data/sfx2-dialogs-test.txt");
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(Sfx2DialogsTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/qa/unoapi/knownissues.xcl b/sfx2/qa/unoapi/knownissues.xcl
new file mode 100644
index 0000000000..1d87f84c96
--- /dev/null
+++ b/sfx2/qa/unoapi/knownissues.xcl
@@ -0,0 +1,5 @@
+### i23244 ###
+sfx.FrameLoader::com::sun::star::frame::XSynchronousFrameLoader
+
+### i79149 ###
+sfx.StandaloneDocumentInfo::com::sun::star::document::DocumentInfo
diff --git a/sfx2/qa/unoapi/sfx.sce b/sfx2/qa/unoapi/sfx.sce
new file mode 100644
index 0000000000..d75b95b31b
--- /dev/null
+++ b/sfx2/qa/unoapi/sfx.sce
@@ -0,0 +1,4 @@
+-o sfx.AppDispatchProvider
+#i113306 -o sfx.DocumentTemplates
+-o sfx.FrameLoader
+-o sfx.SfxMacroLoader
diff --git a/sfx2/qa/unoapi/testdocuments/SfxStandaloneDocInfoObject.sdw b/sfx2/qa/unoapi/testdocuments/SfxStandaloneDocInfoObject.sdw
new file mode 100644
index 0000000000..c4b5672f96
--- /dev/null
+++ b/sfx2/qa/unoapi/testdocuments/SfxStandaloneDocInfoObject.sdw
Binary files differ
diff --git a/sfx2/qa/unoapi/testdocuments/report.stw b/sfx2/qa/unoapi/testdocuments/report.stw
new file mode 100644
index 0000000000..5b8efafa15
--- /dev/null
+++ b/sfx2/qa/unoapi/testdocuments/report.stw
Binary files differ
diff --git a/sfx2/qa/unoapi/testdocuments/report2.stw b/sfx2/qa/unoapi/testdocuments/report2.stw
new file mode 100644
index 0000000000..9ee0a7ee0e
--- /dev/null
+++ b/sfx2/qa/unoapi/testdocuments/report2.stw
Binary files differ