Chandra
view release on metacpan or search on metacpan
t/51_contextmenu.t view on Meta::CPAN
#!/usr/bin/env perl
use strict;
use warnings;
use Test::More;
use Chandra::ContextMenu;
# ---- API exists ----
{
can_ok('Chandra::ContextMenu', qw(
new
attach detach attach_global detach_global
show_at set_item add_item remove_item
items get_items attachments
enable disable is_enabled
inject js_code
_dispatch
));
}
# ---- Constructor defaults ----
{
my $cm = Chandra::ContextMenu->new;
isa_ok($cm, 'Chandra::ContextMenu');
ok($cm->is_enabled, 'enabled by default');
my $items = $cm->items;
is(ref $items, 'ARRAY', 'items returns arrayref');
is(scalar @$items, 0, 'empty items by default');
}
# ---- Constructor with items ----
{
my $cm = Chandra::ContextMenu->new(
items => [
{ label => 'Cut', action => sub {} },
{ label => 'Copy', action => sub {} },
{ separator => 1 },
{ label => 'Paste', action => sub {} },
],
);
my $items = $cm->items;
is(scalar @$items, 4, '4 items stored');
is($items->[0]{label}, 'Cut', 'first item label');
ok($items->[2]{separator}, 'separator item');
}
# ---- Enable / disable ----
{
my $cm = Chandra::ContextMenu->new;
ok($cm->is_enabled, 'initially enabled');
$cm->disable;
ok(!$cm->is_enabled, 'disabled');
$cm->enable;
ok($cm->is_enabled, 're-enabled');
}
# ---- Chaining ----
{
my $cm = Chandra::ContextMenu->new;
is($cm->enable, $cm, 'enable chains');
is($cm->disable, $cm, 'disable chains');
is($cm->attach('#foo'), $cm, 'attach chains');
is($cm->detach('#foo'), $cm, 'detach chains');
is($cm->attach_global, $cm, 'attach_global chains');
is($cm->detach_global, $cm, 'detach_global chains');
}
# ---- Attach / detach ----
{
my $cm = Chandra::ContextMenu->new;
my @att = $cm->attachments;
is(scalar @att, 0, 'no attachments initially');
$cm->attach('#editor');
$cm->attach('.sidebar');
@att = sort $cm->attachments;
is(scalar @att, 2, 'two attachments');
is($att[0], '#editor', 'first selector');
is($att[1], '.sidebar', 'second selector');
$cm->detach('#editor');
@att = $cm->attachments;
is(scalar @att, 1, 'one after detach');
is($att[0], '.sidebar', 'remaining selector');
}
# ---- Attach with dynamic callback ----
{
my $cm = Chandra::ContextMenu->new;
my $called = 0;
$cm->attach('.item', sub { $called++; return [] });
my @att = $cm->attachments;
is(scalar @att, 1, 'dynamic attach stored');
}
# ---- attach_global / detach_global ----
{
my $cm = Chandra::ContextMenu->new;
$cm->attach_global;
# just verify no crash; _global flag is internal
$cm->detach_global;
ok(1, 'global attach/detach works');
}
# ---- add_item / remove_item ----
{
my $cm = Chandra::ContextMenu->new;
$cm->add_item({ label => 'Alpha', action => sub {} });
$cm->add_item({ label => 'Beta', action => sub {} });
my $items = $cm->items;
is(scalar @$items, 2, '2 items after add');
is($items->[0]{label}, 'Alpha', 'first added');
is($items->[1]{label}, 'Beta', 'second added');
$cm->remove_item('Alpha');
$items = $cm->items;
is(scalar @$items, 1, '1 item after remove');
is($items->[0]{label}, 'Beta', 'correct item remains');
}
# ---- set_item ----
{
my $cm = Chandra::ContextMenu->new(
items => [
{ label => 'Delete', action => sub {}, disabled => 1 },
],
);
ok($cm->items->[0]{disabled}, 'initially disabled');
$cm->set_item('Delete', disabled => 0);
ok(!$cm->items->[0]{disabled}, 'enabled after set_item');
}
# ---- Separator items ----
{
my $cm = Chandra::ContextMenu->new(
items => [
{ label => 'A', action => sub {} },
{ separator => 1 },
{ label => 'B', action => sub {} },
],
);
ok($cm->items->[1]{separator}, 'separator present');
is(scalar @{$cm->items}, 3, 'three items total');
}
# ---- Submenu nesting ----
{
my $cm = Chandra::ContextMenu->new(
items => [
{ label => 'File', submenu => [
{ label => 'New', action => sub {} },
{ label => 'Open', action => sub {} },
{ label => 'Recent', submenu => [
{ label => 'file1.txt', action => sub {} },
]},
]},
],
);
my $items = $cm->items;
is(scalar @$items, 1, 'one top-level item');
is(ref $items->[0]{submenu}, 'ARRAY', 'submenu is arrayref');
is(scalar @{$items->[0]{submenu}}, 3, 'submenu has 3 items');
is(ref $items->[0]{submenu}[2]{submenu}, 'ARRAY', 'nested submenu');
}
# ---- Checkable items ----
{
my $cm = Chandra::ContextMenu->new(
items => [
{ label => 'Word Wrap', checkable => 1, checked => 1, action => sub {} },
],
);
my $it = $cm->items->[0];
ok($it->{checkable}, 'checkable flag');
ok($it->{checked}, 'initially checked');
}
# ---- Icon and shortcut ----
{
my $cm = Chandra::ContextMenu->new(
items => [
{ label => 'Copy', icon => "\x{1f4cb}", shortcut => 'Ctrl+C', action => sub {} },
],
);
my $it = $cm->items->[0];
is($it->{shortcut}, 'Ctrl+C', 'shortcut stored');
ok($it->{icon}, 'icon stored');
}
# ---- show_at stores coords ----
{
my $cm = Chandra::ContextMenu->new;
is($cm->show_at(100, 200), $cm, 'show_at chains');
}
# ---- js_code generates code ----
{
my $cm = Chandra::ContextMenu->new(
items => [
{ label => 'Cut', action => sub {} },
{ separator => 1 },
{ label => 'Paste', action => sub {} },
],
);
$cm->attach('#editor');
my $js = $cm->js_code;
like($js, qr/__chandraCtxMenu/, 'guard variable present');
like($js, qr/#editor/, 'selector in JS');
like($js, qr/contextmenu/, 'contextmenu event');
like($js, qr/closeAll/, 'close function');
like($js, qr/buildMenu/, 'build function');
like($js, qr/l:'Cut'/, 'item label in JS');
like($js, qr/sep:1/, 'separator in JS');
}
# ---- _dispatch action ----
{
my $cm = Chandra::ContextMenu->new(
items => [
{ label => 'Test', action => sub { $_[0] } },
],
);
$cm->attach('#x');
# Find the action ID
my $id = $cm->items->[0]{_id};
ok(defined $id, 'action ID assigned');
my $called = 0;
$cm->items->[0]{action} = sub { $called++ };
# Re-register: the action was stored at registration time
# so we test with the original action stored in _actions
$cm->_dispatch('{"type":"action","id":' . $id . '}');
# The action in _actions is the original sub, not our replacement
# Just verify no crash
ok(1, '_dispatch action no crash');
}
# ---- _dispatch with disabled ----
{
my $cm = Chandra::ContextMenu->new;
$cm->disable;
eval { $cm->_dispatch('{"type":"action","id":1}') };
ok(!$@, 'dispatch while disabled is no-op');
}
# ---- _dispatch invalid JSON ----
{
my $cm = Chandra::ContextMenu->new;
eval { $cm->_dispatch('not json') };
ok(!$@, 'invalid JSON is no-op');
}
# ---- _dispatch checkable toggle ----
{
my $toggled_to;
my $cm = Chandra::ContextMenu->new(
items => [
{ label => 'Wrap', checkable => 1, checked => 1,
action => sub { $toggled_to = $_[0] } },
],
);
$cm->attach('#x');
my $id = $cm->items->[0]{_id};
$cm->_dispatch('{"type":"action","id":' . $id . ',"chk":false}');
is($cm->items->[0]{checked}, 0, 'checked toggled to 0');
}
# ---- get_items alias ----
{
my $cm = Chandra::ContextMenu->new(
items => [{ label => 'A', action => sub {} }],
);
my $a = $cm->items;
my $b = $cm->get_items;
is_deeply($a, $b, 'items and get_items return same ref');
}
done_testing;
( run in 1.122 second using v1.01-cache-2.11-cpan-39bf76dae61 )