Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bugfix#30 #31

Merged
merged 5 commits into from Apr 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGES
@@ -1,6 +1,9 @@
Revision history for Perl distribution Win32-Mechanize-NotepadPlusPlus

v0.002001 2020-Mar-21
v0.002002 (internal:2020-Apr-02)
- runPluginsCommand() = fixed command cache feature and improved test (#30)

v0.002001 (internal:2020-Mar-21)
- test suite bugfix: add delay in myTestHelpers to make sure there's
enough time after Notepad++ started before trying to save the
session (to avoid race condition with no files loaded yet)
Expand Down
8 changes: 6 additions & 2 deletions lib/Win32/Mechanize/NotepadPlusPlus.pm
Expand Up @@ -5,7 +5,7 @@ use strict;
use Exporter 'import';
use Carp;

our $VERSION = '0.002001'; # rrr.mmmsss : rrr is major revision; mmm is minor revision; sss is sub-revision; optionally use _sss instead, for alpha sub-releases
our $VERSION = '0.002002'; # rrr.mmmsss : rrr is major revision; mmm is minor revision; sss is sub-revision; optionally use _sss instead, for alpha sub-releases

use Win32::Mechanize::NotepadPlusPlus::Notepad ':vars';
use Win32::Mechanize::NotepadPlusPlus::Editor ':vars';
Expand Down Expand Up @@ -37,7 +37,7 @@ our @EXPORT_OTHER = qw//; # maybe eventually, functions to create and destroy
our @EXPORT_OK = (@EXPORT_MAIN, @EXPORT_VARS, @EXPORT_OTHER);
our %EXPORT_TAGS = (
main => [@EXPORT_MAIN],
other => [@EXPORT_OTHER],
#other => [@EXPORT_OTHER],
vars => [@EXPORT_VARS],
all => [@EXPORT_OK],
);
Expand Down Expand Up @@ -136,6 +136,10 @@ L<Win32::Mechanize::NotepadPlusPlus::Editor::Messages>
# from Notepad::Messages: %NPPMSG, %VIEW, %MODELESS, %STATUSBAR, %MENUHANDLE, %INTERNALVAR, %LANGTYPE, %WINVER, %WINPLATFORM, %NOTIFICATION, %DOCSTATUS, %NPPIDM, %ENCODINGKEY
# from Editor::Messages: %SCIMSG, %SCINTILLANOTIFICATION, and more than 50 others.

=item :all

Exports everything from L</":main"> and L</":vars">.

=back

=head1 LIMITATIONS
Expand Down
2 changes: 1 addition & 1 deletion lib/Win32/Mechanize/NotepadPlusPlus/Editor.pm
Expand Up @@ -10,7 +10,7 @@ use Win32::Mechanize::NotepadPlusPlus::Editor::Messages; # exports %scimsg, whi
use utf8; # there are UTF8 arrows throughout the source code (in POD and strings)
use Config;

our $VERSION = '0.002001'; # auto-populated from W::M::NPP
our $VERSION = '0.002002'; # auto-populated from W::M::NPP

our @EXPORT_VARS = (@Win32::Mechanize::NotepadPlusPlus::Editor::Messages::EXPORT);
our @EXPORT_OK = (@EXPORT_VARS);
Expand Down
19 changes: 11 additions & 8 deletions lib/Win32/Mechanize/NotepadPlusPlus/Notepad.pm
Expand Up @@ -31,7 +31,7 @@ BEGIN {
Win32::API::->Import("psapi","BOOL EnumProcessModules(HANDLE hProcess, HMODULE *lphModule, DWORD cb, LPDWORD lpcbNeeded)") or die "EnumProcessModules: $^E"; # uncoverable branch true
}

our $VERSION = '0.002001'; # auto-populated from W::M::NPP
our $VERSION = '0.002002'; # auto-populated from W::M::NPP

our @EXPORT_VARS = (@Win32::Mechanize::NotepadPlusPlus::Notepad::Messages::EXPORT);
our @EXPORT_OK = (@EXPORT_VARS);
Expand Down Expand Up @@ -1470,23 +1470,26 @@ sub runMenuCommand {
# 2019-Oct-14: see debug\menuNav.pl for my attempt to find a specific menu; I will need to add caching here, as well as add test coverage...
# It appears 'menuOption' was meant to be a submenu item; in which case, I might want to collapse it down, or otherwise determine whether the third argument is passed or not
# https://github.com/bruderstein/PythonScript/blob/1d9230ffcb2c110918c1c9d36176bcce0a6572b6/PythonScript/src/NotepadPlusWrapper.cpp#L865
# printf STDERR "\n__%04d__:runMenuCommand(%s)\n", __LINE__, join ", ", map { defined $_ ? qq('$_') : '<undef>'} @_;
#printf STDERR "\n__%04d__:runMenuCommand(%s)\n", __LINE__, join ", ", map { defined $_ ? qq('$_') : '<undef>'} @_;
my %opts = ();
%opts = %{pop(@_)} if (ref($_[-1]) && UNIVERSAL::isa($_[-1],'hash'));
$opts{refreshCache} = 0 unless exists $opts{refreshCache};

# printf STDERR "\n__%04d__:runMenuCommand(%s, {refreshCache => %s)\n", __LINE__, join(", ", map { defined $_ ? qq('$_') : '<undef>'} @_), $opts{refreshCache};
#printf STDERR "\n__%04d__:runMenuCommand(%s, {refreshCache => %s)\n", __LINE__, join(", ", map { defined $_ ? qq('$_') : '<undef>'} @_), $opts{refreshCache};

## printf STDERR "\n__%04d__:\tcacheMenuCommands = (%s\n\t\t)\n", __LINE__, join("\n\t\t\t", '', map { "'$_' => '$cacheMenuCommands{$_}'" } keys %cacheMenuCommands);

my $cacheKey = undef;
my $action;
if(!$opts{refreshCache}) {
$cacheKey = join ' | ', @_;
return $cacheMenuCommands{$cacheKey} if exists $cacheMenuCommands{$cacheKey};
$action = $cacheMenuCommands{$cacheKey} if exists $cacheMenuCommands{$cacheKey};
}

# printf STDERR "__%04d__:\tcacheKey = '%s'\n", __LINE__, $cacheKey // '<undef>';
#printf STDERR "__%04d__:\tcacheKey = '%s'\n", __LINE__, $cacheKey // '<undef>';

my $action = _findActionInMenu( $self->{_menuID} , @_ );
# printf STDERR "__%04d__:\taction(%s) = '%s'\n", __LINE__, $self->{_menuID} // '<undef>', $action // '<undef>';
$action //= _findActionInMenu( $self->{_menuID} , @_ );
#printf STDERR "__%04d__:\taction(%s) = '%s'\n", __LINE__, $self->{_menuID} // '<undef>', $action // '<undef>';
return undef unless defined $action; # pass the problem up the chain

$cacheMenuCommands{$cacheKey} = $action if defined($cacheKey) and defined $action;
Expand All @@ -1498,7 +1501,7 @@ sub runMenuCommand {
$NPPMSG{WM_COMMAND} = Win32::GuiTest::WM_COMMAND unless exists $NPPMSG{WM_COMMAND};
return $self->SendMessage( $NPPMSG{WM_COMMAND} , $action, 0);

# https://stackoverflow.com/questions/18589385/retrieve-list-of-menu-items-in-windows-in-c
#exit https://stackoverflow.com/questions/18589385/retrieve-list-of-menu-items-in-windows-in-c
}

=item runPluginCommand
Expand Down
6 changes: 3 additions & 3 deletions t/myTestHelpers.pm
Expand Up @@ -106,10 +106,10 @@ sub __runCodeAndClickPopup {
if(!defined $pid) { # failed
die "fork failed: $!";
} elsif(!$pid) { # child: pid==0
my $f = WaitWindowLike(0, $re, undef, undef, 3, 10);
my $f = WaitWindowLike(0, $re, undef, undef, 3, 10); # parent, title, class, id, depth, wait
my $p = GetParent($f);
if($DEBUG_INFO) {
note "runCodeAndClickPopup(..., $re, $n, $xtraDelay):\n";
note "runCodeAndClickPopup(..., /$re/, n:$n, delay:$xtraDelay): ", scalar(localtime), "\n";
note sprintf qq|\tfound: %d t:"%s" c:"%s"\n\tparent: %d t:"%s" c:"%s"\n|,
$f, GetWindowText($f), GetClassName($f),
$p, GetWindowText($p), GetClassName($p),
Expand All @@ -134,7 +134,7 @@ sub __runCodeAndClickPopup {
print "caller($i): ", join(', ', @c), $/;
}
}
my $h = $buttons[$n];
my $h = $buttons[$n] // 0;
my $id = GetWindowID($h);
if($DEBUG_INFO) { note sprintf "\tCHOSEN:\t%d t:'%s' c:'%s' id=%d\n", $h, GetWindowText($h), GetClassName($h), $id; }
sleep($xtraDelay) if $xtraDelay;
Expand Down
113 changes: 4 additions & 109 deletions t/npp-gui.t
Expand Up @@ -204,114 +204,9 @@ local $TODO = undef;
is $ret, undef, 'prompt(): cancel: retval is undef'; note sprintf qq(\t=> "%s"\n), $ret // '<undef>';
}

# menuCommand
{
my $ret = notepad()->menuCommand('IDM_VIEW_CLONE_TO_ANOTHER_VIEW');
ok $ret, 'menuCommand("IDM_VIEW_CLONE_TO_ANOTHER_VIEW"): retval from string-param'; note sprintf qq(\t=> "0x%08x"\n), $ret // '<undef>';

# close the cloned window, which also tests value-based menuCommand...
$ret = notepad()->menuCommand($NPPIDM{IDM_FILE_CLOSE});
ok $ret, 'menuCommand(NPPIDM{IDM_FILE_CLOSE}): retval from value-param'; note sprintf qq(\t=> "0x%08x"\n), $ret // '<undef>';
}

# runMenuCommand
{
# for runMenuCommand, I am going to SHA-256 on active selection; which means I need a selection, and need to know what it is.
my $expected = 'a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e';
my $algorithm = 'SHA-256';

# 1. create new file
notepad()->newFile();
select undef,undef,undef,0.25;

# 2. add known text
editor()->{_hwobj}->SendMessage_sendRawString( $SCIMSG{SCI_SETTEXT}, 0, "Hello World" );
select undef,undef,undef,0.25;

# 3. select that text
notepad()->menuCommand('IDM_EDIT_SELECTALL');
select undef,undef,undef,0.25;

# 4. run the menu command
my $ret = notepad()->runMenuCommand( "Tools | $algorithm", 'Generate from selection into clipboard');
unless(defined $ret) {
$algorithm = 'MD5';
$expected = 'b10a8db164e0754105b7a99be72e3fe5';
$ret = notepad()->runMenuCommand( "Tools | $algorithm", 'Generate from selection into clipboard');
}
ok $ret, "runMenuCommand(Tools | $algorithm | Generate from selection into clipboard): retval"; note sprintf qq(\t=> "%s"\n), $ret // '<undef>';

# 5. paste the resulting text
notepad()->menuCommand('IDM_EDIT_PASTE');

# 6. get the resulting textlength and text
my $len = editor()->{_hwobj}->SendMessage( $SCIMSG{SCI_GETTEXTLENGTH} ); note sprintf qq(\t=> "%s"\n), $len // '<undef>';
{
my $txt;
eval {
$txt = editor()->{_hwobj}->SendMessage_getRawString( $SCIMSG{SCI_GETTEXT}, $len+1, { trim => 'wparam' } );
} or do {
diag "eval(getRawString) = '$@'";
$txt = '';
};
$txt =~ s/[\0\s]+$//; # remove trailing spaces and nulls
is $txt, $expected, "runMenuCommand(): resulting $algorithm text"; note sprintf qq(\t%s => "%s"\n), $algorithm, $txt // '<undef>';
}

# 7. clear the editor, so I can close without a dialog
editor()->{_hwobj}->SendMessage_sendRawString( $SCIMSG{SCI_SETTEXT}, 0, "\0" );

# 8. close
notepad()->close();
}

# runPluginCommand
{
# for runPluginCommand, I cannot guarantee the presence of any give plugin, so (until I have the ability to add to menu) try to just do Plugins Admin dialog
# some experimenting showed (..., qr/^Plugins Admin$/, 4) as the appropriate args
my $ret;
myTestHelpers->setDebugInfo(0);
runCodeAndClickPopup( sub { $ret = notepad()->runPluginCommand( 'Plugins Admin...') }, qr/^Plugins Admin$/, 4, 1 ); # wait an extra 1s before pushing the button, which makes it more reliable

if(defined $ret) {
ok $ret, 'runPluginCommand(Plugins | Plugins Admin...): retval' or diag sprintf qq(\t=> "%s"\n), $ret // '<undef>';
} else {
use Win32::GuiTest 1.64 qw':FUNC !SendMessage';
diag "runPluginCommand(Plugins Admin...) didn't work, and I don't know why... Trying alternative";
my $menuID = notepad()->{_menuID}; note "notepad()->{_menuID} = ", $menuID//'<undef>';
my $count = GetMenuItemCount( $menuID ); note "GetMenuItemCount() = ", $count // '<undef>';
my $submenu;
for my $idx ( 0 .. $count-1 ) {
my %h = GetMenuItemInfo( $menuID, $idx );
if( $h{type} eq 'string' ) {
(my $cleanText = $h{text}) =~ s/(\&|\t.*)//;
note sprintf "\t%-20s | %s\n", $h{text}, $cleanText;
$submenu = GetSubMenu($menuID, $idx) if $cleanText eq 'Plugins';
}
}
note sprintf "Plugins submenu #%s#\n", $submenu // '<undef>';
my $does_have_folder;
my $does_have_admin;
if(defined $submenu) {
note "submenu GetMenuItemCount() = ", my $count = GetMenuItemCount( $submenu ) // '<undef>';
for my $idx ( 0 .. $count-1 ) {
my %h = GetMenuItemInfo( $submenu, $idx );
if( $h{type} eq 'string' ) {
(my $cleanText = $h{text}) =~ s/(\&|\t.*)//;
note sprintf "\t%-20s | %s\n", $h{text}, $cleanText;
$does_have_admin = 1 if $cleanText =~ /Plugins Admin/;
$does_have_folder = 1 if $cleanText =~ /Open Plugins Folder/;
}
}
}
ok !$does_have_admin, 'Plugins | Plugins Admin should not exist, because runPluginCommand(Plugins | Plugins Admin) didnt work'; diag sprintf "\tdoes have admin = %s", $does_have_admin//'<undef>';

if($does_have_folder) {
$ret = notepad()->runPluginCommand('Open Plugins Folder...');
ok $ret, 'runPluginCommand(Plugins | Open Plugins Folder...): retval' or diag sprintf qq(\t=> "%s"\n), $ret // '<undef>';
diag "Sorry for opening the extra Explorer window. You may close it now.\n";
}
}
}
# moved to npp-menucmd.t:
# menuCommand
# runMenuCommand
# runPluginCommand

done_testing;
155 changes: 155 additions & 0 deletions t/npp-menucmd.t
@@ -0,0 +1,155 @@
########################################################################
# Verifies Notepad object messages / methods work
# subgroup: those necessary for menuCommands
########################################################################
use 5.010;
use strict;
use warnings;
use Test::More;
use Win32;
use Win32::GuiTest 1.64 qw':FUNC !SendMessage';

use FindBin;
use lib $FindBin::Bin;
use myTestHelpers;
myTestHelpers::setChildEndDelay(6);

use Path::Tiny 0.018 qw/path tempfile/;

use Win32::Mechanize::NotepadPlusPlus qw/:main :vars/;


# menuCommand
{
my $ret = notepad()->menuCommand('IDM_VIEW_CLONE_TO_ANOTHER_VIEW');
ok $ret, 'menuCommand("IDM_VIEW_CLONE_TO_ANOTHER_VIEW"): retval from string-param'; note sprintf qq(\t=> "0x%08x"\n), $ret // '<undef>';

# close the cloned window, which also tests value-based menuCommand...
$ret = notepad()->menuCommand($NPPIDM{IDM_FILE_CLOSE});
ok $ret, 'menuCommand(NPPIDM{IDM_FILE_CLOSE}): retval from value-param'; note sprintf qq(\t=> "0x%08x"\n), $ret // '<undef>';
}

# runMenuCommand
{
# for runMenuCommand, I am going to SHA-256 on active selection; which means I need a selection, and need to know what it is.
my $expected = 'a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e';
my $algorithm = 'SHA-256';

# 1. create new file
notepad()->newFile();
select undef,undef,undef,0.25;

# 2. add known text
editor()->{_hwobj}->SendMessage_sendRawString( $SCIMSG{SCI_SETTEXT}, 0, "Hello World" );
select undef,undef,undef,0.25;

# 3. select that text
notepad()->menuCommand('IDM_EDIT_SELECTALL');
select undef,undef,undef,0.25;

# 4. run the menu command
my $ret = notepad()->runMenuCommand( "Tools | $algorithm", 'Generate from selection into clipboard');
unless(defined $ret) {
$algorithm = 'MD5';
$expected = 'b10a8db164e0754105b7a99be72e3fe5';
$ret = notepad()->runMenuCommand( "Tools | $algorithm", 'Generate from selection into clipboard');
}
ok $ret, "runMenuCommand(Tools | $algorithm | Generate from selection into clipboard): retval"; note sprintf qq(\t=> "%s"\n), $ret // '<undef>';

# 5. paste the resulting text
notepad()->menuCommand('IDM_EDIT_PASTE');

# 6. get the resulting textlength and text
my $len = editor()->{_hwobj}->SendMessage( $SCIMSG{SCI_GETTEXTLENGTH} ); note sprintf qq(\t=> "%s"\n), $len // '<undef>';
{
my $txt;
eval {
$txt = editor()->{_hwobj}->SendMessage_getRawString( $SCIMSG{SCI_GETTEXT}, $len+1, { trim => 'wparam' } );
} or do {
diag "eval(getRawString) = '$@'";
$txt = '';
};
$txt =~ s/[\0\s]+$//; # remove trailing spaces and nulls
is $txt, $expected, "runMenuCommand(): resulting $algorithm text"; note sprintf qq(\t%s => "%s"\n), $algorithm, $txt // '<undef>';
}

# 7. clear the editor, so I can close without a dialog
editor()->{_hwobj}->SendMessage_sendRawString( $SCIMSG{SCI_SETTEXT}, 0, "\0" );

# 8. close
notepad()->close();
}

# runPluginCommand
# 2020-Apr-03: per issue#30, need to rework the test suite, to make sure caching is working right, etc
# new outline:
# * compare ->{_menuID} to getMainMenuHandle
# * verify Plugins menu exists in main menu
# * if not, exit
# * ok ->getPluginMenuHandle()
# new:
SKIP: {
# won't actually skip until partway through, but by wrapping the whole sequence in
# the SKIP: block, I can jump out at one or more spots without having to manually control that
# the only thing i have to do is keep the number of skipped tests correct
local $TODO;
my $remaining = 6;

# make sure main menu ID matches
my $str = "Main Menu Handle";
my $exp = notepad->{_menuID};
my $got = notepad->getMainMenuHandle();
is $got, $exp, sprintf '%s: from message vs GetMenu(hwnd)', $str;
note sprintf "\t%s: expected = GetMenu() = %s\n", $str, $exp;
note sprintf "\t%s: got = getMainMenuHandle() = %s\n", $str, $got;
--$remaining;

# plugin menu handle
$str = "Plugin Menu Handle";
my $pluginID = notepad->getPluginMenuHandle();
ok defined $got, sprintf '%s: defined handle returned', $str;
note sprintf "\t%s: got = getMainMenuHandle() = %s\n", $str, $got;
--$remaining;
skip "No $str found", $remaining unless $got;
ok $got, sprintf '%s: reasonable handle value', $str;

# plugins menu contents
$str = "Plugin Menu Contents";
my $count = GetMenuItemCount( $pluginID );
note sprintf "\t%s: got %s items\n", $str, $count//'<undef>';

my %plugin_entries;
for my $idx ( 0 .. $count-1 ) {
my %h = GetMenuItemInfo( $pluginID, $idx );
if( $h{type} eq 'string' ) {
(my $cleanText = $h{text}) =~ s/(\&|\t.*)//;
note sprintf "\t\t%-20s | %s\n", $h{text}, $cleanText;
$plugin_entries{$cleanText} = GetSubMenu($pluginID, $idx);
}
}

myTestHelpers->setDebugInfo(1);
TODO: for my $arr (
["Plugins Admin...", "Plugins Admin", 4],
["Converter", "About", "Converter Plugin", 0],
) {
$str = $arr->[0];
my $btn_num = pop @$arr;
my $title = pop @$arr;
my $t_extra = 1; # 1s extra delay
local $TODO = "couldn't find '$str'" unless exists $plugin_entries{ $str };
my $re = qr/^\Q$title\E$/;
for(1..2) {
my $ret;
runCodeAndClickPopup( sub { $ret = notepad()->runPluginCommand( @$arr ) }, $re, $btn_num, $t_extra );
ok $ret//'<undef>', sprintf "%s [#%s]: ret=%s", $str, $_, $ret//'<undef>';
--$remaining;
sleep( $t_extra );
}
}
myTestHelpers->setDebugInfo(0);

skip "NEED TO FIX initial \$remaining value", $remaining if $remaining>0;
}

done_testing(11);