<?php
/**
* SeekQuarry/Yioop --
* Open Source Pure PHP Search Engine, Crawler, and Indexer
*
* Copyright (C) 2009 - 2026 Chris Pollett chris@pollett.org
*
* LICENSE:
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* END LICENSE
*
* @author Chris Pollett chris@pollett.org
* @license https://www.gnu.org/licenses/ GPL3
* @link https://www.seekquarry.com/
* @copyright 2009 - 2026
* @filesource
*/
namespace seekquarry\yioop\tests;
use seekquarry\yioop\library\mail\ImapFolderListParser;
use seekquarry\yioop\library\UnitTest;
/**
* Unit tests for ImapFolderListParser. Each test feeds untagged
* LIST response lines shaped the way a real IMAP server sends them
* and checks that the folder names and their selectable flags come
* back correctly. The cases cover a normal quoted folder, a
* \Noselect hierarchy folder, a folder whose name arrives as a {N}
* literal, a NIL hierarchy delimiter, and a non-LIST line that must
* be skipped.
*
* @author Chris Pollett
*/
class ImapFolderListParserTest extends UnitTest
{
/**
* No setup state is needed; the parser is pure and static.
*/
public function setUp()
{
}
/**
* No setup state is needed; nothing to tear down.
*/
public function tearDown()
{
}
/**
* A normal quoted folder name with ordinary attributes: it
* should parse to its name and be marked selectable.
*/
public function normalQuotedFolderTestCase()
{
$lines = ['* LIST (\\HasNoChildren) "/" "INBOX"'];
$folders = ImapFolderListParser::parse($lines);
$this->assertEqual(1, count($folders),
"One LIST line yields one folder");
$this->assertEqual("INBOX", $folders[0]["NAME"],
"The quoted folder name is parsed");
$this->assertTrue($folders[0]["SELECTABLE"],
"A folder without \\Noselect is selectable");
}
/**
* A folder carrying the \Noselect attribute exists only as a
* point in the hierarchy and must come back not selectable.
*/
public function noselectFolderTestCase()
{
$lines = ['* LIST (\\Noselect \\HasChildren) "/" "Archive"'];
$folders = ImapFolderListParser::parse($lines);
$this->assertEqual(1, count($folders),
"The \\Noselect line still yields a folder entry");
$this->assertEqual("Archive", $folders[0]["NAME"],
"The \\Noselect folder name is parsed");
$this->assertFalse($folders[0]["SELECTABLE"],
"A \\Noselect folder is not selectable");
}
/**
* A folder whose name arrives as a {N} literal, the form a
* server uses when the name contains characters awkward to
* quote. ImapClient::send() stitches the payload inline.
*/
public function literalFolderNameTestCase()
{
$lines = ['* LIST (\\HasNoChildren) "/" {9}Sent Mail'];
$folders = ImapFolderListParser::parse($lines);
$this->assertEqual(1, count($folders),
"The literal-named line yields one folder");
$this->assertEqual("Sent Mail", $folders[0]["NAME"],
"A {N} literal folder name is read by byte count");
}
/**
* A flat mailbox with no hierarchy sends NIL for the delimiter.
* The NIL delimiter token must be consumed so the folder name
* after it is still read correctly.
*/
public function nilDelimiterTestCase()
{
$lines = ['* LIST (\\HasNoChildren) NIL "Drafts"'];
$folders = ImapFolderListParser::parse($lines);
$this->assertEqual(1, count($folders),
"A NIL-delimiter line still yields a folder");
$this->assertEqual("Drafts", $folders[0]["NAME"],
"The folder name after a NIL delimiter is parsed");
}
/**
* A response carrying lines that are not LIST responses, such
* as the tagged completion line: the non-LIST lines must be
* skipped and only the real LIST entries returned.
*/
public function nonListLineSkippedTestCase()
{
$lines = [
'* LIST (\\HasNoChildren) "/" "INBOX"',
'* STATUS "INBOX" (MESSAGES 42)',
'A001 OK LIST completed',
];
$folders = ImapFolderListParser::parse($lines);
$this->assertEqual(1, count($folders),
"Only the real LIST line becomes a folder");
$this->assertEqual("INBOX", $folders[0]["NAME"],
"The surviving folder is the one from the LIST line");
}
/**
* A folder carrying an RFC 6154 special-use attribute must have
* that attribute surfaced in SPECIAL_USE; a folder with no such
* attribute must come back with an empty SPECIAL_USE.
*/
public function specialUseAttributeTestCase()
{
$lines = [
'* LIST (\\HasNoChildren \\Sent) "." "Sent"',
'* LIST (\\HasNoChildren) "." "Notes"',
];
$folders = ImapFolderListParser::parse($lines);
$this->assertEqual("\\Sent", $folders[0]["SPECIAL_USE"],
"The \\Sent special-use attribute is captured");
$this->assertEqual("", $folders[1]["SPECIAL_USE"],
"A folder with no special-use attribute reports none");
}
/**
* sortBySpecialUse must order a scrambled folder list as INBOX
* first, then the special-use folders in their conventional
* sequence, then the remaining folders alphabetically. This
* setup mirrors a real server that marks two different
* mailboxes \Sent; both must land in the Sent slot, ordered
* alphabetically against each other.
*/
public function sortBySpecialUseTestCase()
{
$lines = [
'* LIST (\\HasNoChildren \\Archive) "." "Archive"',
'* LIST (\\HasNoChildren \\Trash) "." "Trash"',
'* LIST (\\HasNoChildren \\Sent) "." "Sent Messages"',
'* LIST (\\HasNoChildren) "." "Notes"',
'* LIST (\\HasNoChildren \\Sent) "." "Sent"',
'* LIST (\\HasNoChildren \\Drafts) "." "Drafts"',
'* LIST (\\HasChildren) "." "INBOX"',
];
$sorted = ImapFolderListParser::sortBySpecialUse(
ImapFolderListParser::parse($lines));
$order = [];
foreach ($sorted as $folder) {
$order[] = $folder["NAME"];
}
$expected = ["INBOX", "Drafts", "Sent", "Sent Messages",
"Archive", "Trash", "Notes"];
$this->assertEqual($expected, $order,
"Folders sort INBOX-first, then by special-use role, " .
"then alphabetically, with same-role folders ordered " .
"alphabetically among themselves");
}
}