Repository: PHPOffice/PhpSpreadsheet (php)

Issue:
Include sheet state in listWorksheetInfo
The method `listWorksheetInfo` is great for analyzing an Excel workbook without loading it (and therefore consuming a lot of memory). Sadly, it is missing the sheet state of the worksheets. Adding the sheet state to the output of `listWorksheetInfo` would make reasoning about workbooks and their worksheets more flexibel.

Pull request:
Add sheetState to ListWorksheetInfo Data
Fix #4345. Add a new item to the output array. Although the output is changed, this does not seem like a breaking change to me.

This is:

- [ ] a bugfix
- [x] a new feature
- [ ] refactoring
- [ ] additional unit tests

Checklist:

- [ ] Changes are covered by unit tests
  - [ ] Changes are covered by existing unit tests
  - [x] New unit tests have been added
- [x] Code style is respected
- [x] Commit message explains **why** the change is made (see https://github.com/erlang/otp/wiki/Writing-good-commit-messages)
- [ ] CHANGELOG.md contains a short summary of the change and a link to the pull request if applicable
- [ ] Documentation is updated as necessary

### Why this change is needed?

Provide an explanation of why this change is needed, with links to any Issues (if appropriate).
If this is a bugfix or a new feature, and there are no existing Issues, then please also create an issue that will make it easier to track progress with this PR.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5957fac33..4fcbb0dfd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
 
 ### Changed
 
+- ListWorksheetInfo will now return sheetState (visible, hidden, veryHidden). [Issue #4345](https://github.com/PHPOffice/PhpSpreadsheet/issues/4345) [PR #4366](https://github.com/PHPOffice/PhpSpreadsheet/pull/4366)
 - Start migration to Phpstan 2. [PR #4359](https://github.com/PHPOffice/PhpSpreadsheet/pull/4359)
 - IOFactory identify can return, and createReader and CreateWriter can accept, a class name rather than a file type. [Issue #4357](https://github.com/PHPOffice/PhpSpreadsheet/issues/4357) [PR #4361](https://github.com/PHPOffice/PhpSpreadsheet/pull/4361)
 
diff --git a/src/PhpSpreadsheet/Reader/Csv.php b/src/PhpSpreadsheet/Reader/Csv.php
index 9455993e8..e15675378 100644
--- a/src/PhpSpreadsheet/Reader/Csv.php
+++ b/src/PhpSpreadsheet/Reader/Csv.php
@@ -252,6 +252,7 @@ class Csv extends BaseReader
 
         $worksheetInfo[0]['lastColumnLetter'] = Coordinate::stringFromColumnIndex($worksheetInfo[0]['lastColumnIndex'] + 1);
         $worksheetInfo[0]['totalColumns'] = $worksheetInfo[0]['lastColumnIndex'] + 1;
+        $worksheetInfo[0]['sheetState'] = Worksheet::SHEETSTATE_VISIBLE;
 
         // Close file
         fclose($fileHandle);
diff --git a/src/PhpSpreadsheet/Reader/Gnumeric.php b/src/PhpSpreadsheet/Reader/Gnumeric.php
index ed81efb22..6b71da029 100644
--- a/src/PhpSpreadsheet/Reader/Gnumeric.php
+++ b/src/PhpSpreadsheet/Reader/Gnumeric.php
@@ -33,6 +33,9 @@ class Gnumeric extends BaseReader
 
     const NAMESPACE_OOO = 'http://openoffice.org/2004/office';
 
+    const GNM_SHEET_VISIBILITY_VISIBLE = 'GNM_SHEET_VISIBILITY_VISIBLE';
+    const GNM_SHEET_VISIBILITY_HIDDEN = 'GNM_SHEET_VISIBILITY_HIDDEN';
+
     /**
      * Shared Expressions.
      */
@@ -144,7 +147,12 @@ class Gnumeric extends BaseReader
                     'lastColumnIndex' => 0,
                     'totalRows' => 0,
                     'totalColumns' => 0,
+                    'sheetState' => Worksheet::SHEETSTATE_VISIBLE,
                 ];
+                $visibility = $xml->getAttribute('Visibility');
+                if ((string) $visibility === self::GNM_SHEET_VISIBILITY_HIDDEN) {
+                    $tmpInfo['sheetState'] = Worksheet::SHEETSTATE_HIDDEN;
+                }
 
                 while ($xml->read()) {
                     if (self::matchXml($xml, 'Name')) {
@@ -271,8 +279,8 @@ class Gnumeric extends BaseReader
             //        name in line with the formula, not the reverse
             $this->spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false);
 
-            $visibility = $sheet->attributes()['Visibility'] ?? 'GNM_SHEET_VISIBILITY_VISIBLE';
-            if ((string) $visibility !== 'GNM_SHEET_VISIBILITY_VISIBLE') {
+            $visibility = $sheet->attributes()['Visibility'] ?? self::GNM_SHEET_VISIBILITY_VISIBLE;
+            if ((string) $visibility !== self::GNM_SHEET_VISIBILITY_VISIBLE) {
                 $this->spreadsheet->getActiveSheet()->setSheetState(Worksheet::SHEETSTATE_HIDDEN);
             }
 
diff --git a/src/PhpSpreadsheet/Reader/Html.php b/src/PhpSpreadsheet/Reader/Html.php
index 2f4ef4f13..0ac63fda6 100644
--- a/src/PhpSpreadsheet/Reader/Html.php
+++ b/src/PhpSpreadsheet/Reader/Html.php
@@ -1232,6 +1232,7 @@ class Html extends BaseReader
             $newEntry['lastColumnIndex'] = Coordinate::columnIndexFromString($sheet->getHighestDataColumn()) - 1;
             $newEntry['totalRows'] = $sheet->getHighestDataRow();
             $newEntry['totalColumns'] = $newEntry['lastColumnIndex'] + 1;
+            $newEntry['sheetState'] = Worksheet::SHEETSTATE_VISIBLE;
             $info[] = $newEntry;
         }
         $spreadsheet->disconnectWorksheets();
diff --git a/src/PhpSpreadsheet/Reader/Ods.php b/src/PhpSpreadsheet/Reader/Ods.php
index 57111a9c2..bcc5d4fd3 100644
--- a/src/PhpSpreadsheet/Reader/Ods.php
+++ b/src/PhpSpreadsheet/Reader/Ods.php
@@ -157,64 +157,67 @@ class Ods extends BaseReader
 
         // Step into the first level of content of the XML
         $xml->read();
+        $tableVisibility = [];
+        $lastTableStyle = '';
+
         while ($xml->read()) {
-            // Quickly jump through to the office:body node
-            while (self::getXmlName($xml) !== 'office:body') {
-                if ($xml->isEmptyElement) {
-                    $xml->read();
-                } else {
-                    $xml->next();
+            if (self::getXmlName($xml) === 'style:style') {
+                $styleType = $xml->getAttribute('style:family');
+                if ($styleType === 'table') {
+                    $lastTableStyle = $xml->getAttribute('style:name');
                 }
-            }
-            // Now read each node until we find our first table:table node
-            while ($xml->read()) {
-                if (self::getXmlName($xml) == 'table:table' && $xml->nodeType == XMLReader::ELEMENT) {
-                    $worksheetNames[] = $xml->getAttribute('table:name');
-
-                    $tmpInfo = [
-                        'worksheetName' => $xml->getAttribute('table:name'),
-                        'lastColumnLetter' => 'A',
-                        'lastColumnIndex' => 0,
-                        'totalRows' => 0,
-                        'totalColumns' => 0,
-                    ];
-
-                    // Loop through each child node of the table:table element reading
-                    $currCells = 0;
-                    do {
+            } elseif (self::getXmlName($xml) === 'style:table-properties') {
+                $visibility = $xml->getAttribute('table:display');
+                $tableVisibility[$lastTableStyle] = ($visibility === 'false') ? Worksheet::SHEETSTATE_HIDDEN : Worksheet::SHEETSTATE_VISIBLE;
+            } elseif (self::getXmlName($xml) == 'table:table' && $xml->nodeType == XMLReader::ELEMENT) {
+                $worksheetNames[] = $xml->getAttribute('table:name');
+
+                $styleName = $xml->getAttribute('table:style-name') ?? '';
+                $visibility = $tableVisibility[$styleName] ?? '';
+                $tmpInfo = [
+                    'worksheetName' => $xml->getAttribute('table:name'),
+                    'lastColumnLetter' => 'A',
+                    'lastColumnIndex' => 0,
+                    'totalRows' => 0,
+                    'totalColumns' => 0,
+                    'sheetState' => $visibility,
+                ];
+
+                // Loop through each child node of the table:table element reading
+                $currCells = 0;
+                do {
+                    $xml->read();
+                    if (self::getXmlName($xml) == 'table:table-row' && $xml->nodeType == XMLReader::ELEMENT) {
+                        $rowspan = $xml->getAttribute('table:number-rows-repeated');
+                        $rowspan = empty($rowspan) ? 1 : $rowspan;
+                        $tmpInfo['totalRows'] += $rowspan;
+                        $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells);
+                        $currCells = 0;
+                        // Step into the row
                         $xml->read();
-                        if (self::getXmlName($xml) == 'table:table-row' && $xml->nodeType == XMLReader::ELEMENT) {
-                            $rowspan = $xml->getAttribute('table:number-rows-repeated');
-                            $rowspan = empty($rowspan) ? 1 : $rowspan;
-                            $tmpInfo['totalRows'] += $rowspan;
-                            $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells);
-                            $currCells = 0;
-                            // Step into the row
-                            $xml->read();
-                            do {
-                                $doread = true;
-                                if (self::getXmlName($xml) == 'table:table-cell' && $xml->nodeType == XMLReader::ELEMENT) {
-                                    if (!$xml->isEmptyElement) {
-                                        ++$currCells;
-                                        $xml->next();
-                                        $doread = false;
-                                    }
-                                } elseif (self::getXmlName($xml) == 'table:covered-table-cell' && $xml->nodeType == XMLReader::ELEMENT) {
-                                    $mergeSize = $xml->getAttribute('table:number-columns-repeated');
-                                    $currCells += (int) $mergeSize;
+                        do {
+                            $doread = true;
+                            if (self::getXmlName($xml) == 'table:table-cell' && $xml->nodeType == XMLReader::ELEMENT) {
+                                if (!$xml->isEmptyElement) {
+                                    ++$currCells;
+                                    $xml->next();
+                                    $doread = false;
                                 }
-                                if ($doread) {
-                                    $xml->read();
-                                }
-                            } while (self::getXmlName($xml) != 'table:table-row');
-                        }
-                    } while (self::getXmlName($xml) != 'table:table');
+                            } elseif (self::getXmlName($xml) == 'table:covered-table-cell' && $xml->nodeType == XMLReader::ELEMENT) {
+                                $mergeSize = $xml->getAttribute('table:number-columns-repeated');
+                                $currCells += (int) $mergeSize;
+                            }
+                            if ($doread) {
+                                $xml->read();
+                            }
+                        } while (self::getXmlName($xml) != 'table:table-row');
+                    }
+                } while (self::getXmlName($xml) != 'table:table');
 
-                    $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells);
-                    $tmpInfo['lastColumnIndex'] = $tmpInfo['totalColumns'] - 1;
-                    $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1);
-                    $worksheetInfo[] = $tmpInfo;
-                }
+                $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells);
+                $tmpInfo['lastColumnIndex'] = $tmpInfo['totalColumns'] - 1;
+                $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1);
+                $worksheetInfo[] = $tmpInfo;
             }
         }
 
diff --git a/src/PhpSpreadsheet/Reader/Slk.php b/src/PhpSpreadsheet/Reader/Slk.php
index ce26eca20..abd0adf5c 100644
--- a/src/PhpSpreadsheet/Reader/Slk.php
+++ b/src/PhpSpreadsheet/Reader/Slk.php
@@ -131,6 +131,7 @@ class Slk extends BaseReader
         $worksheetInfo[0]['totalRows'] = $rowIndex;
         $worksheetInfo[0]['lastColumnLetter'] = Coordinate::stringFromColumnIndex($worksheetInfo[0]['lastColumnIndex'] + 1);
         $worksheetInfo[0]['totalColumns'] = $worksheetInfo[0]['lastColumnIndex'] + 1;
+        $worksheetInfo[0]['sheetState'] = Worksheet::SHEETSTATE_VISIBLE;
 
         // Close file
         fclose($fileHandle);
diff --git a/src/PhpSpreadsheet/Reader/Xls/ListFunctions.php b/src/PhpSpreadsheet/Reader/Xls/ListFunctions.php
index 6477606b0..ab9849a7a 100644
--- a/src/PhpSpreadsheet/Reader/Xls/ListFunctions.php
+++ b/src/PhpSpreadsheet/Reader/Xls/ListFunctions.php
@@ -5,6 +5,7 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xls;
 use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
 use PhpOffice\PhpSpreadsheet\Reader\Xls;
 use PhpOffice\PhpSpreadsheet\Shared\File;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
 
 class ListFunctions extends Xls
 {
@@ -104,6 +105,7 @@ class ListFunctions extends Xls
             $tmpInfo['lastColumnIndex'] = 0;
             $tmpInfo['totalRows'] = 0;
             $tmpInfo['totalColumns'] = 0;
+            $tmpInfo['sheetState'] = $sheet['sheetState'];
 
             $xls->pos = $sheet['offset'];
 
diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php
index e7cd0b711..6b93c9763 100644
--- a/src/PhpSpreadsheet/Reader/Xlsx.php
+++ b/src/PhpSpreadsheet/Reader/Xlsx.php
@@ -238,6 +238,8 @@ class Xlsx extends BaseReader
                             'totalRows' => 0,
                             'totalColumns' => 0,
                         ];
+                        $sheetState = (string) (self::getAttributes($eleSheet)['state'] ?? Worksheet::SHEETSTATE_VISIBLE);
+                        $tmpInfo['sheetState'] = $sheetState;
 
                         $fileWorksheet = (string) $worksheets[self::getArrayItemString(self::getAttributes($eleSheet, $namespace), 'id')];
                         $fileWorksheetPath = str_starts_with($fileWorksheet, '/') ? substr($fileWorksheet, 1) : "$dir/$fileWorksheet";
@@ -257,7 +259,7 @@ class Xlsx extends BaseReader
                         $currCells = 0;
                         while ($xml->read()) {
                             if ($xml->localName == 'row' && $xml->nodeType == XMLReader::ELEMENT && $xml->namespaceURI === $mainNS) {
-                                $row = $xml->getAttribute('r');
+                                $row = (int) $xml->getAttribute('r');
                                 $tmpInfo['totalRows'] = $row;
                                 $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells);
                                 $currCells = 0;
diff --git a/src/PhpSpreadsheet/Reader/Xml.php b/src/PhpSpreadsheet/Reader/Xml.php
index 5e9830f43..0a54182d7 100644
--- a/src/PhpSpreadsheet/Reader/Xml.php
+++ b/src/PhpSpreadsheet/Reader/Xml.php
@@ -229,6 +229,7 @@ class Xml extends BaseReader
 
             $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1);
             $tmpInfo['totalColumns'] = $tmpInfo['lastColumnIndex'] + 1;
+            $tmpInfo['sheetState'] = Worksheet::SHEETSTATE_VISIBLE;
 
             $worksheetInfo[] = $tmpInfo;
             ++$worksheetID;


Test patch:
diff --git a/tests/PhpSpreadsheetTests/Reader/Gnumeric/GnumericInfoTest.php b/tests/PhpSpreadsheetTests/Reader/Gnumeric/GnumericInfoTest.php
index 74b9a8bde..be0174d26 100644
--- a/tests/PhpSpreadsheetTests/Reader/Gnumeric/GnumericInfoTest.php
+++ b/tests/PhpSpreadsheetTests/Reader/Gnumeric/GnumericInfoTest.php
@@ -32,6 +32,7 @@ class GnumericInfoTest extends TestCase
                 'lastColumnIndex' => 13,
                 'totalRows' => 31,
                 'totalColumns' => 14,
+                'sheetState' => 'visible',
             ],
             [
                 'worksheetName' => 'Report Data',
@@ -39,6 +40,7 @@ class GnumericInfoTest extends TestCase
                 'lastColumnIndex' => 10,
                 'totalRows' => 65535,
                 'totalColumns' => 11,
+                'sheetState' => 'visible',
             ],
         ];
         self::assertEquals($expected, $info);
diff --git a/tests/PhpSpreadsheetTests/Reader/Gnumeric/HiddenWorksheetTest.php b/tests/PhpSpreadsheetTests/Reader/Gnumeric/HiddenWorksheetTest.php
index 021ece1cb..92459adce 100644
--- a/tests/PhpSpreadsheetTests/Reader/Gnumeric/HiddenWorksheetTest.php
+++ b/tests/PhpSpreadsheetTests/Reader/Gnumeric/HiddenWorksheetTest.php
@@ -49,4 +49,29 @@ class HiddenWorksheetTest extends TestCase
             ],
         ];
     }
+
+    public function testListWorksheetInfo(): void
+    {
+        $filename = 'tests/data/Reader/Gnumeric/HiddenSheet.gnumeric';
+        $reader = new Gnumeric();
+        $expected = [
+            [
+                'worksheetName' => 'Sheet1',
+                'lastColumnLetter' => 'A',
+                'lastColumnIndex' => 0,
+                'totalRows' => 1,
+                'totalColumns' => 1,
+                'sheetState' => 'visible',
+            ],
+            [
+                'worksheetName' => 'Sheet2',
+                'lastColumnLetter' => 'A',
+                'lastColumnIndex' => 0,
+                'totalRows' => 1,
+                'totalColumns' => 1,
+                'sheetState' => 'hidden',
+            ],
+        ];
+        self::assertSame($expected, $reader->listWorksheetInfo($filename));
+    }
 }
diff --git a/tests/PhpSpreadsheetTests/Reader/Ods/HiddenWorksheetTest.php b/tests/PhpSpreadsheetTests/Reader/Ods/HiddenWorksheetTest.php
index 9793a3c09..988947f0e 100644
--- a/tests/PhpSpreadsheetTests/Reader/Ods/HiddenWorksheetTest.php
+++ b/tests/PhpSpreadsheetTests/Reader/Ods/HiddenWorksheetTest.php
@@ -12,7 +12,7 @@ class HiddenWorksheetTest extends TestCase
 {
     public function testPageSetup(): void
     {
-        $filename = 'tests/data/Reader/Ods/HiddenSheet.ods';
+        $filename = 'tests/data/Reader/Ods/HiddenSheet2.ods';
         $reader = new Ods();
         $spreadsheet = $reader->load($filename);
         $assertions = $this->worksheetAssertions();
@@ -49,4 +49,29 @@ class HiddenWorksheetTest extends TestCase
             ],
         ];
     }
+
+    public function testListWorksheetInfo(): void
+    {
+        $filename = 'tests/data/Reader/Ods/HiddenSheet2.ods';
+        $reader = new Ods();
+        $expected = [
+            [
+                'worksheetName' => 'Sheet1',
+                'lastColumnLetter' => 'A',
+                'lastColumnIndex' => 0,
+                'totalRows' => 1,
+                'totalColumns' => 1,
+                'sheetState' => 'visible',
+            ],
+            [
+                'worksheetName' => 'Sheet2',
+                'lastColumnLetter' => 'A',
+                'lastColumnIndex' => 0,
+                'totalRows' => 1,
+                'totalColumns' => 1,
+                'sheetState' => 'hidden',
+            ],
+        ];
+        self::assertSame($expected, $reader->listWorksheetInfo($filename));
+    }
 }
diff --git a/tests/PhpSpreadsheetTests/Reader/Ods/OdsInfoTest.php b/tests/PhpSpreadsheetTests/Reader/Ods/OdsInfoTest.php
index e780d32b6..03e80356c 100644
--- a/tests/PhpSpreadsheetTests/Reader/Ods/OdsInfoTest.php
+++ b/tests/PhpSpreadsheetTests/Reader/Ods/OdsInfoTest.php
@@ -70,6 +70,7 @@ class OdsInfoTest extends TestCase
                 'lastColumnIndex' => 2,
                 'totalRows' => 12,
                 'totalColumns' => 3,
+                'sheetState' => 'visible',
             ],
             [
                 'worksheetName' => 'Second Sheet',
@@ -77,6 +78,7 @@ class OdsInfoTest extends TestCase
                 'lastColumnIndex' => 0,
                 'totalRows' => 2,
                 'totalColumns' => 1,
+                'sheetState' => 'visible',
             ],
         ], $wsinfo);
     }
diff --git a/tests/PhpSpreadsheetTests/Reader/Xls/HiddenWorksheetTest.php b/tests/PhpSpreadsheetTests/Reader/Xls/HiddenWorksheetTest.php
index 904843923..3a2949a51 100644
--- a/tests/PhpSpreadsheetTests/Reader/Xls/HiddenWorksheetTest.php
+++ b/tests/PhpSpreadsheetTests/Reader/Xls/HiddenWorksheetTest.php
@@ -49,4 +49,45 @@ class HiddenWorksheetTest extends TestCase
             ],
         ];
     }
+
+    public function testListWorksheetInfo(): void
+    {
+        $filename = 'tests/data/Reader/XLS/visibility.xls';
+        $reader = new Xls();
+        $expected = [
+            [
+                'worksheetName' => 'Sheet1',
+                'lastColumnLetter' => 'A',
+                'lastColumnIndex' => 0,
+                'totalRows' => 1,
+                'totalColumns' => 1,
+                'sheetState' => 'visible',
+            ],
+            [
+                'worksheetName' => 'Sheet2',
+                'lastColumnLetter' => 'A',
+                'lastColumnIndex' => 0,
+                'totalRows' => 1,
+                'totalColumns' => 1,
+                'sheetState' => 'hidden',
+            ],
+            [
+                'worksheetName' => 'Sheet3',
+                'lastColumnLetter' => 'A',
+                'lastColumnIndex' => 0,
+                'totalRows' => 1,
+                'totalColumns' => 1,
+                'sheetState' => 'visible',
+            ],
+            [
+                'worksheetName' => 'Sheet4',
+                'lastColumnLetter' => 'A',
+                'lastColumnIndex' => 0,
+                'totalRows' => 2,
+                'totalColumns' => 1,
+                'sheetState' => 'veryHidden',
+            ],
+        ];
+        self::assertSame($expected, $reader->listWorksheetInfo($filename));
+    }
 }
diff --git a/tests/PhpSpreadsheetTests/Reader/Xls/InfoNamesTest.php b/tests/PhpSpreadsheetTests/Reader/Xls/InfoNamesTest.php
index 4f9073a7b..6a92a83a6 100644
--- a/tests/PhpSpreadsheetTests/Reader/Xls/InfoNamesTest.php
+++ b/tests/PhpSpreadsheetTests/Reader/Xls/InfoNamesTest.php
@@ -31,6 +31,7 @@ class InfoNamesTest extends TestCase
                 'lastColumnIndex' => 4,
                 'totalRows' => 19,
                 'totalColumns' => 5,
+                'sheetState' => 'visible',
             ],
             [
                 'worksheetName' => 'Terms and conditions',
@@ -38,6 +39,7 @@ class InfoNamesTest extends TestCase
                 'lastColumnIndex' => 1,
                 'totalRows' => 3,
                 'totalColumns' => 2,
+                'sheetState' => 'visible',
             ],
         ];
         self::assertSame($expected, $info);
@@ -66,6 +68,7 @@ class InfoNamesTest extends TestCase
                 'lastColumnIndex' => 1,
                 'totalRows' => 1,
                 'totalColumns' => 2,
+                'sheetState' => 'visible',
             ],
         ];
         self::assertSame($expected, $info);
@@ -106,6 +109,7 @@ class InfoNamesTest extends TestCase
                 'lastColumnIndex' => 15,
                 'totalRows' => 3,
                 'totalColumns' => 16,
+                'sheetState' => 'visible',
             ],
         ];
         self::assertSame($expected, $info);
diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/HiddenWorksheetTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/HiddenWorksheetTest.php
index b464cb6cf..f6017a120 100644
--- a/tests/PhpSpreadsheetTests/Reader/Xlsx/HiddenWorksheetTest.php
+++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/HiddenWorksheetTest.php
@@ -46,4 +46,45 @@ class HiddenWorksheetTest extends TestCase
             ],
         ];
     }
+
+    public function testListWorksheetInfo(): void
+    {
+        $filename = 'tests/data/Reader/XLSX/visibility.xlsx';
+        $reader = new Xlsx();
+        $expected = [
+            [
+                'worksheetName' => 'Sheet1',
+                'lastColumnLetter' => 'A',
+                'lastColumnIndex' => 0,
+                'totalRows' => 1,
+                'totalColumns' => 1,
+                'sheetState' => 'visible',
+            ],
+            [
+                'worksheetName' => 'Sheet2',
+                'lastColumnLetter' => 'A',
+                'lastColumnIndex' => 0,
+                'totalRows' => 1,
+                'totalColumns' => 1,
+                'sheetState' => 'hidden',
+            ],
+            [
+                'worksheetName' => 'Sheet3',
+                'lastColumnLetter' => 'A',
+                'lastColumnIndex' => 0,
+                'totalRows' => 1,
+                'totalColumns' => 1,
+                'sheetState' => 'visible',
+            ],
+            [
+                'worksheetName' => 'Sheet4',
+                'lastColumnLetter' => 'B',
+                'lastColumnIndex' => 1,
+                'totalRows' => 1,
+                'totalColumns' => 2,
+                'sheetState' => 'veryHidden',
+            ],
+        ];
+        self::assertSame($expected, $reader->listWorksheetInfo($filename));
+    }
 }
diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue2516Test.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue2516Test.php
index c4333d6f3..2d5d3901c 100644
--- a/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue2516Test.php
+++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue2516Test.php
@@ -52,8 +52,9 @@ class Issue2516Test extends TestCase
                 'worksheetName' => 'Sheet1',
                 'lastColumnLetter' => 'B',
                 'lastColumnIndex' => 1,
-                'totalRows' => '6',
+                'totalRows' => 6,
                 'totalColumns' => 2,
+                'sheetState' => 'visible',
             ],
         ];
         self::assertSame($expected, $infos);
diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespacePurlTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespacePurlTest.php
index 5e1e6a567..77e136378 100644
--- a/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespacePurlTest.php
+++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespacePurlTest.php
@@ -40,8 +40,9 @@ class NamespacePurlTest extends \PHPUnit\Framework\TestCase
                 'worksheetName' => 'ml_out',
                 'lastColumnLetter' => 'R',
                 'lastColumnIndex' => 17,
-                'totalRows' => '76',
+                'totalRows' => 76,
                 'totalColumns' => 18,
+                'sheetState' => 'visible',
             ],
         ];
 
diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/WorksheetInfoNamesTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/WorksheetInfoNamesTest.php
index 4c56339fb..fa6fe9a86 100644
--- a/tests/PhpSpreadsheetTests/Reader/Xlsx/WorksheetInfoNamesTest.php
+++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/WorksheetInfoNamesTest.php
@@ -22,6 +22,7 @@ class WorksheetInfoNamesTest extends TestCase
                 'lastColumnIndex' => 5,
                 'totalRows' => '6',
                 'totalColumns' => 6,
+                'sheetState' => 'visible',
             ],
         ];
 
@@ -51,6 +52,7 @@ class WorksheetInfoNamesTest extends TestCase
                 'lastColumnIndex' => 10,
                 'totalRows' => 2,
                 'totalColumns' => 11,
+                'sheetState' => 'visible',
             ],
         ];
 
diff --git a/tests/PhpSpreadsheetTests/Reader/Xml/XmlInfoTest.php b/tests/PhpSpreadsheetTests/Reader/Xml/XmlInfoTest.php
index 623fc8be9..5eb2149bc 100644
--- a/tests/PhpSpreadsheetTests/Reader/Xml/XmlInfoTest.php
+++ b/tests/PhpSpreadsheetTests/Reader/Xml/XmlInfoTest.php
@@ -52,6 +52,7 @@ class XmlInfoTest extends TestCase
                 'lastColumnIndex' => 9,
                 'totalRows' => 31,
                 'totalColumns' => 10,
+                'sheetState' => 'visible',
             ],
             [
                 'worksheetName' => 'Report Data',
@@ -59,6 +60,7 @@ class XmlInfoTest extends TestCase
                 'lastColumnIndex' => 8,
                 'totalRows' => 15,
                 'totalColumns' => 9,
+                'sheetState' => 'visible',
             ],
         ];
         self::assertEquals($expected, $info);